ragbits-evaluate 0.0.30.dev29302392__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.
- ragbits/evaluate/__init__.py +0 -0
- ragbits/evaluate/agent_simulation/__init__.py +87 -0
- ragbits/evaluate/agent_simulation/context.py +140 -0
- ragbits/evaluate/agent_simulation/conversation.py +333 -0
- ragbits/evaluate/agent_simulation/deepeval_evaluator.py +92 -0
- ragbits/evaluate/agent_simulation/logger.py +165 -0
- ragbits/evaluate/agent_simulation/metrics/__init__.py +19 -0
- ragbits/evaluate/agent_simulation/metrics/builtin.py +221 -0
- ragbits/evaluate/agent_simulation/metrics/collectors.py +142 -0
- ragbits/evaluate/agent_simulation/models.py +37 -0
- ragbits/evaluate/agent_simulation/results.py +200 -0
- ragbits/evaluate/agent_simulation/scenarios.py +129 -0
- ragbits/evaluate/agent_simulation/simulation.py +245 -0
- ragbits/evaluate/cli.py +150 -0
- ragbits/evaluate/config.py +11 -0
- ragbits/evaluate/dataloaders/__init__.py +3 -0
- ragbits/evaluate/dataloaders/base.py +95 -0
- ragbits/evaluate/dataloaders/document_search.py +61 -0
- ragbits/evaluate/dataloaders/exceptions.py +25 -0
- ragbits/evaluate/dataloaders/gaia.py +78 -0
- ragbits/evaluate/dataloaders/hotpot_qa.py +95 -0
- ragbits/evaluate/dataloaders/human_eval.py +70 -0
- ragbits/evaluate/dataloaders/question_answer.py +56 -0
- ragbits/evaluate/dataset_generator/__init__.py +0 -0
- ragbits/evaluate/dataset_generator/pipeline.py +141 -0
- ragbits/evaluate/dataset_generator/prompts/__init__.py +0 -0
- ragbits/evaluate/dataset_generator/prompts/corpus_generation.py +21 -0
- ragbits/evaluate/dataset_generator/prompts/qa.py +83 -0
- ragbits/evaluate/dataset_generator/tasks/__init__.py +0 -0
- ragbits/evaluate/dataset_generator/tasks/corpus_generation.py +67 -0
- ragbits/evaluate/dataset_generator/tasks/filter/__init__.py +0 -0
- ragbits/evaluate/dataset_generator/tasks/filter/base.py +43 -0
- ragbits/evaluate/dataset_generator/tasks/filter/dont_know.py +34 -0
- ragbits/evaluate/dataset_generator/tasks/text_generation/__init__.py +0 -0
- ragbits/evaluate/dataset_generator/tasks/text_generation/base.py +66 -0
- ragbits/evaluate/dataset_generator/tasks/text_generation/qa.py +96 -0
- ragbits/evaluate/dataset_generator/utils.py +43 -0
- ragbits/evaluate/evaluator.py +244 -0
- ragbits/evaluate/factories/__init__.py +42 -0
- ragbits/evaluate/metrics/__init__.py +3 -0
- ragbits/evaluate/metrics/base.py +89 -0
- ragbits/evaluate/metrics/document_search.py +90 -0
- ragbits/evaluate/metrics/gaia.py +84 -0
- ragbits/evaluate/metrics/hotpot_qa.py +51 -0
- ragbits/evaluate/metrics/human_eval.py +105 -0
- ragbits/evaluate/metrics/question_answer.py +205 -0
- ragbits/evaluate/optimizer.py +210 -0
- ragbits/evaluate/pipelines/__init__.py +37 -0
- ragbits/evaluate/pipelines/base.py +64 -0
- ragbits/evaluate/pipelines/document_search.py +106 -0
- ragbits/evaluate/pipelines/gaia.py +249 -0
- ragbits/evaluate/pipelines/hotpot_qa.py +342 -0
- ragbits/evaluate/pipelines/human_eval.py +323 -0
- ragbits/evaluate/pipelines/question_answer.py +96 -0
- ragbits/evaluate/py.typed +0 -0
- ragbits/evaluate/utils.py +160 -0
- ragbits_evaluate-0.0.30.dev29302392.dist-info/METADATA +58 -0
- ragbits_evaluate-0.0.30.dev29302392.dist-info/RECORD +59 -0
- ragbits_evaluate-0.0.30.dev29302392.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from datasets import Dataset
|
|
5
|
+
from distilabel.pipeline import Pipeline
|
|
6
|
+
from distilabel.steps.base import Step
|
|
7
|
+
from omegaconf import DictConfig, OmegaConf
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
from ragbits.core.utils.config_handling import import_by_path
|
|
11
|
+
|
|
12
|
+
module = sys.modules[__name__]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class LLMConfigForTask(BaseModel):
|
|
16
|
+
"""
|
|
17
|
+
Configuration for the LLM (Language Model) associated with a specific task.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
provider_type (str): The type of LLM provider.
|
|
21
|
+
kwargs (dict): Additional parameters or settings for the LLM provider.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
provider_type: str
|
|
25
|
+
kwargs: dict
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TaskConfig(BaseModel):
|
|
29
|
+
"""
|
|
30
|
+
Configuration for an individual task in the dataset generation pipeline.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
type: str: type of the task
|
|
34
|
+
llm (LLMConfigForTask): The configuration for the LLM used in this task.
|
|
35
|
+
kwargs (dicts): Optional additional parameters or settings for the task.
|
|
36
|
+
filters (list[str] | None): Optional filters to apply during the task. Defaults to None.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
type: str
|
|
40
|
+
llm: LLMConfigForTask
|
|
41
|
+
kwargs: dict | None = None
|
|
42
|
+
filters: list[str] | None = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class DatasetGenerationPipelineConfig(BaseModel):
|
|
46
|
+
"""
|
|
47
|
+
Configuration for the entire dataset generation pipeline.
|
|
48
|
+
|
|
49
|
+
Attributes:
|
|
50
|
+
name (str): The name of the dataset generation pipeline.
|
|
51
|
+
input_name (str): The name of the input resource or dataset.
|
|
52
|
+
tasks (list[TaskConfig]): A list of task configurations included in the pipeline.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
name: str
|
|
56
|
+
input_name: str
|
|
57
|
+
tasks: list[TaskConfig]
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def from_dict_config(cls, dict_config: DictConfig) -> "DatasetGenerationPipelineConfig":
|
|
61
|
+
"""
|
|
62
|
+
Creates an instance of `DatasetGenerationPipelineConfig` from a dictionary-based configuration.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
dict_config (DictConfig): A configuration object containing pipeline details.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
DatasetGenerationPipelineConfig: An instance populated with data from the given configuration.
|
|
69
|
+
|
|
70
|
+
"""
|
|
71
|
+
name = dict_config.name
|
|
72
|
+
input_name = dict_config.input_name
|
|
73
|
+
tasks = [
|
|
74
|
+
TaskConfig(
|
|
75
|
+
type=task_config.type,
|
|
76
|
+
llm=LLMConfigForTask(
|
|
77
|
+
provider_type=task_config.llm.provider_type,
|
|
78
|
+
kwargs=OmegaConf.to_container(task_config.llm.kwargs), # type: ignore
|
|
79
|
+
),
|
|
80
|
+
kwargs=OmegaConf.to_container(task_config.kwargs), # type: ignore
|
|
81
|
+
filters=getattr(task_config, "filters", None),
|
|
82
|
+
)
|
|
83
|
+
for task_config in dict_config.tasks
|
|
84
|
+
]
|
|
85
|
+
return cls(name=name, input_name=input_name, tasks=tasks)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class DatasetGenerationPipeline:
|
|
89
|
+
"""A pipeline for dataset generation"""
|
|
90
|
+
|
|
91
|
+
def __init__(self, config: DatasetGenerationPipelineConfig):
|
|
92
|
+
self.config = config
|
|
93
|
+
self._instantiate_pipeline()
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def from_dict_config(cls, dict_config: DictConfig) -> "DatasetGenerationPipeline":
|
|
97
|
+
"""
|
|
98
|
+
Instantiates the pipeline from dict config validated through pydantic base model
|
|
99
|
+
Returns:
|
|
100
|
+
DatasetGenerationPipeline
|
|
101
|
+
"""
|
|
102
|
+
config = DatasetGenerationPipelineConfig.from_dict_config(dict_config=dict_config)
|
|
103
|
+
return cls(config=config)
|
|
104
|
+
|
|
105
|
+
def __call__(self, corpus: list[str]) -> Dataset:
|
|
106
|
+
"""
|
|
107
|
+
Generates a dataset from a corpus or list of topics
|
|
108
|
+
Args:
|
|
109
|
+
corpus: a corpus of information or list of topics
|
|
110
|
+
Returns:
|
|
111
|
+
dataset instance
|
|
112
|
+
"""
|
|
113
|
+
dataset = Dataset.from_dict({self.config.input_name: corpus})
|
|
114
|
+
distiset = self.pipeline.run(use_cache=False, dataset=dataset)
|
|
115
|
+
result = distiset["default"]["train"]
|
|
116
|
+
result = result.remove_columns(["distilabel_metadata", "model_name"])
|
|
117
|
+
return result
|
|
118
|
+
|
|
119
|
+
def _parse_pipeline_steps(self) -> list[Step]:
|
|
120
|
+
tasks = []
|
|
121
|
+
for task_config in self.config.tasks:
|
|
122
|
+
llm_config = task_config.llm
|
|
123
|
+
llm = import_by_path(llm_config.provider_type, module)(**llm_config.kwargs)
|
|
124
|
+
task_kwargs: dict[Any, Any] = {"llm": llm}
|
|
125
|
+
task_kwargs.update(task_config.kwargs or {}) # type: ignore
|
|
126
|
+
task = import_by_path(task_config.type, module)(**task_kwargs)
|
|
127
|
+
tasks.append(task)
|
|
128
|
+
filter_types = getattr(task_config, "filters", None) or []
|
|
129
|
+
for filter_type in filter_types:
|
|
130
|
+
filter = import_by_path(filter_type, module)(tasks[-1])
|
|
131
|
+
tasks.append(filter)
|
|
132
|
+
return tasks
|
|
133
|
+
|
|
134
|
+
def _instantiate_pipeline(self) -> None:
|
|
135
|
+
with Pipeline(self.config.name) as self.pipeline:
|
|
136
|
+
tasks = self._parse_pipeline_steps()
|
|
137
|
+
prev_task = None
|
|
138
|
+
for task in tasks:
|
|
139
|
+
if prev_task:
|
|
140
|
+
prev_task >> task
|
|
141
|
+
prev_task = task
|
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
from ragbits.core.prompt import Prompt
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BasicCorpusGenerationPromptInput(BaseModel):
|
|
7
|
+
"""A definition of input for corpus generation task"""
|
|
8
|
+
|
|
9
|
+
query: str
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BasicCorpusGenerationPrompt(Prompt[BasicCorpusGenerationPromptInput]):
|
|
13
|
+
"""A basic prompt for corpus generation"""
|
|
14
|
+
|
|
15
|
+
system_prompt: str = (
|
|
16
|
+
"You are a provider of random factoids on topic requested by a user."
|
|
17
|
+
"Do not write a long essays, the response for given query should be a single sentence"
|
|
18
|
+
"For each query provide only a single fact about a given topic"
|
|
19
|
+
"Use as few tokens as possible"
|
|
20
|
+
)
|
|
21
|
+
user_prompt: str = "Provide factoids about {{ query }}"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
from ragbits.core.prompt import Prompt
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BasicAnswerGenInput(BaseModel):
|
|
7
|
+
"""An input definition for basic answer generation task"""
|
|
8
|
+
|
|
9
|
+
chunk: str
|
|
10
|
+
question: str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BasicAnswerGenPrompt(Prompt[BasicAnswerGenInput, str]):
|
|
14
|
+
"""A prompt clas for basic answers generation"""
|
|
15
|
+
|
|
16
|
+
system_prompt: str = (
|
|
17
|
+
"You are an AI assistant to answer the given question in the provided "
|
|
18
|
+
"evidence text. Do not mention any of these in the answer: 'in the "
|
|
19
|
+
"given text', 'in the provided information', etc. Users do not know "
|
|
20
|
+
"the passage source of the answer, so it should not be mentioned in "
|
|
21
|
+
"the answer. You can find the evidence from the given text about the "
|
|
22
|
+
"question, and you have to write a proper answer to the given question. "
|
|
23
|
+
"If you don't know the answer just say: I don't know."
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
user_prompt: str = "Text:\n<|text_start|>\n {{ chunk }} \n<|text_end|>\n\nQuestion:\n {{ question }} \n\nAnswer:"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class PassagesGenInput(BaseModel):
|
|
30
|
+
"""An input definition to passage generation prompt"""
|
|
31
|
+
|
|
32
|
+
question: str
|
|
33
|
+
basic_answer: str
|
|
34
|
+
chunk: str
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class PassagesGenPrompt(Prompt[PassagesGenInput, str]):
|
|
38
|
+
"""A prompt class for passages generation"""
|
|
39
|
+
|
|
40
|
+
system_prompt: str = (
|
|
41
|
+
"You are an AI tasked with retrieving passages (one or many) from the "
|
|
42
|
+
"provided Chunk that contain information needed to generate the "
|
|
43
|
+
"provided Answer to the given Question.\n\nInstructions:\n1. Each "
|
|
44
|
+
"Passage MUST be VERBATIM and EXACT, without any modifications\n2. "
|
|
45
|
+
"Please provide the response in the form of a Python list. It should "
|
|
46
|
+
"begin with '[' and end with ']'\n3. You MUST start your answer with "
|
|
47
|
+
"'['\n4. The Chunk ALWAYS contains information needed to justify the "
|
|
48
|
+
"Answer\n5. Each passage must be as BRIEF as possible; DO NOT RETURN "
|
|
49
|
+
"FULL SENTENCES"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
user_prompt: str = "Question:\n {{ question }} \nAnswer:\n {{ basic_answer }} \nChunk:\n {{ chunk }}\n\nPassages:"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class QueryGenInput(BaseModel):
|
|
56
|
+
"""An input definition for query generation prompt"""
|
|
57
|
+
|
|
58
|
+
chunk: str
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class QueryGenPrompt(Prompt[QueryGenInput, str]):
|
|
62
|
+
"""A prompt class for query generation"""
|
|
63
|
+
|
|
64
|
+
system_prompt: str = (
|
|
65
|
+
"You're an AI tasked to convert Text into a factoid question. Factoid "
|
|
66
|
+
"questions are those seeking brief, factual information that can be "
|
|
67
|
+
"easily verified. They typically require a yes or no answer or a brief "
|
|
68
|
+
"explanation and often inquire about specific details such as dates, "
|
|
69
|
+
"names, places, or events.\n\nExamples of factoid questions include:\n"
|
|
70
|
+
"- What is the incoming shipment report?\n- What angle should I set my "
|
|
71
|
+
"ladder at?\n- What documents do I need to be a proof of transaction?\n\n"
|
|
72
|
+
"Instructions:\n1. Questions MUST BE extracted from given Text\n2. "
|
|
73
|
+
"Questions MUST BE as SHORT as possible\n3. Questions should be as "
|
|
74
|
+
"detailed as possible from Text\n4. Create questions that ask about "
|
|
75
|
+
"factual information from the Text\n5. Only return ONE question\n6. "
|
|
76
|
+
"Frame questions in a first-person, INFORMAL style, as if the employee "
|
|
77
|
+
"is seeking advice or clarification while working\n7. Do not mention any "
|
|
78
|
+
"of these in the questions: 'in the given text', 'in the provided "
|
|
79
|
+
"information', etc. Users do not know the passage source of the question, "
|
|
80
|
+
"so it should not be mentioned in the question."
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
user_prompt: str = "Text: {{ chunk }}\n\nGenerated Question from the Text:\n"
|
|
File without changes
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import sys
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
|
|
5
|
+
from distilabel.steps import StepInput, StepOutput
|
|
6
|
+
from distilabel.steps.base import Step
|
|
7
|
+
|
|
8
|
+
from ragbits.core.llms.base import LLM
|
|
9
|
+
from ragbits.core.prompt import Prompt
|
|
10
|
+
from ragbits.core.utils.config_handling import import_by_path
|
|
11
|
+
|
|
12
|
+
module = sys.modules[__name__]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CorpusGenerationStep(Step):
|
|
16
|
+
"""A step for corpus generation on given topics"""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
llm: LLM,
|
|
21
|
+
num_per_topic: int,
|
|
22
|
+
prompt_class: str | type[Prompt],
|
|
23
|
+
):
|
|
24
|
+
super().__init__()
|
|
25
|
+
self._llm = llm
|
|
26
|
+
self._prompt_class = import_by_path(prompt_class, module) if isinstance(prompt_class, str) else prompt_class
|
|
27
|
+
self._num_per_topic = num_per_topic
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def inputs(self) -> list[str]:
|
|
31
|
+
"""
|
|
32
|
+
A property defining input fields for a task
|
|
33
|
+
Returns:
|
|
34
|
+
list of input fields
|
|
35
|
+
"""
|
|
36
|
+
return ["query"]
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def outputs(self) -> list[str]:
|
|
40
|
+
"""
|
|
41
|
+
A property describing output fields for a step
|
|
42
|
+
Returns:
|
|
43
|
+
list of output fields
|
|
44
|
+
"""
|
|
45
|
+
return ["chunk"]
|
|
46
|
+
|
|
47
|
+
def process(self, *inputs: StepInput) -> "StepOutput":
|
|
48
|
+
"""
|
|
49
|
+
Generates the corpus data for a given topics
|
|
50
|
+
Args:
|
|
51
|
+
inputs: a topics on which the corpus data should be generated
|
|
52
|
+
Returns:
|
|
53
|
+
a generated corpus
|
|
54
|
+
"""
|
|
55
|
+
result = asyncio.get_event_loop().run_until_complete(self._process_topics(topics=inputs[0]))
|
|
56
|
+
yield result
|
|
57
|
+
|
|
58
|
+
async def _process_topics(self, topics: list[dict]) -> list[dict]:
|
|
59
|
+
tasks = [self._process_topic(topic) for _ in range(self._num_per_topic) for topic in topics]
|
|
60
|
+
results = await asyncio.gather(*tasks)
|
|
61
|
+
return results
|
|
62
|
+
|
|
63
|
+
async def _process_topic(self, topic: dict) -> dict:
|
|
64
|
+
new_inp = deepcopy(topic)
|
|
65
|
+
prompt_inp = self._prompt_class.input_type(**{self.inputs[0]: new_inp[self.inputs[0]]}) # type: ignore
|
|
66
|
+
new_inp[self.outputs[0]] = await self._llm.generate(prompt=self._prompt_class(prompt_inp))
|
|
67
|
+
return new_inp
|
|
File without changes
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
from distilabel.steps import Step, StepInput, StepOutput
|
|
4
|
+
|
|
5
|
+
from ..corpus_generation import CorpusGenerationStep
|
|
6
|
+
from ..text_generation.base import BaseDistilabelTask
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseFilter(Step, ABC):
|
|
10
|
+
"""Base class for filtering the outputs of pipeline steps"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, task: BaseDistilabelTask | CorpusGenerationStep):
|
|
13
|
+
super().__init__()
|
|
14
|
+
self._task = task
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def inputs(self) -> list[str]:
|
|
18
|
+
"""
|
|
19
|
+
Property describing input fields for a filter
|
|
20
|
+
Returns:
|
|
21
|
+
list of input fields for a filter
|
|
22
|
+
"""
|
|
23
|
+
return self._task.outputs
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def outputs(self) -> list[str]:
|
|
27
|
+
"""
|
|
28
|
+
Property describing output fields for a filter
|
|
29
|
+
Returns:
|
|
30
|
+
list of output fields for a filter
|
|
31
|
+
"""
|
|
32
|
+
return self._task.outputs
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def process(self, *inputs: StepInput) -> "StepOutput":
|
|
36
|
+
"""
|
|
37
|
+
Abstract method for filter step processing
|
|
38
|
+
Args:
|
|
39
|
+
inputs - inputs to a filter
|
|
40
|
+
Returns:
|
|
41
|
+
filtered outputs
|
|
42
|
+
"""
|
|
43
|
+
pass
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from distilabel.steps import StepInput, StepOutput
|
|
4
|
+
|
|
5
|
+
from .base import BaseFilter
|
|
6
|
+
|
|
7
|
+
DONT_KNOW_PHRASES: list[str] = [
|
|
8
|
+
"I don't know",
|
|
9
|
+
"I do not know",
|
|
10
|
+
"don't know",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DontKnowFilter(BaseFilter):
|
|
15
|
+
"""A class for basic rule-based filtering of don't know anwers"""
|
|
16
|
+
|
|
17
|
+
def process(self, *inputs: StepInput) -> "StepOutput":
|
|
18
|
+
"""
|
|
19
|
+
Runs the basic rule-based filtering of the inputs
|
|
20
|
+
Args:
|
|
21
|
+
inputs - the outputs of some generation step
|
|
22
|
+
Returns:
|
|
23
|
+
outputs filtered to the ones that do not contain the pre-defined phrases
|
|
24
|
+
"""
|
|
25
|
+
result = [
|
|
26
|
+
{input_type: input_[input_type] for input_type in input_}
|
|
27
|
+
for input_ in inputs[0]
|
|
28
|
+
if not self._is_dont_know(input_)
|
|
29
|
+
]
|
|
30
|
+
yield result
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def _is_dont_know(input_: dict[str, Any]) -> bool:
|
|
34
|
+
return any(s.lower() in input_["basic_answer"].lower() for s in DONT_KNOW_PHRASES)
|
|
File without changes
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from distilabel.models import LLM
|
|
6
|
+
from distilabel.steps.tasks import TextGeneration
|
|
7
|
+
|
|
8
|
+
from ragbits.core.prompt import ChatFormat, Prompt
|
|
9
|
+
from ragbits.core.utils.config_handling import import_by_path
|
|
10
|
+
|
|
11
|
+
module = sys.modules[__name__]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BaseDistilabelTask(TextGeneration, ABC):
|
|
15
|
+
"""Base class for distilabel TextGeneration tasks"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, llm: LLM, inputs: list[str], outputs: list[str], prompt_class: str | type[Prompt]):
|
|
18
|
+
super().__init__(llm=llm)
|
|
19
|
+
self._inputs = inputs
|
|
20
|
+
self._outputs = outputs
|
|
21
|
+
self._prompt_class = import_by_path(prompt_class, module) if isinstance(prompt_class, str) else prompt_class
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def inputs(self) -> list[str]:
|
|
25
|
+
"""
|
|
26
|
+
Property describing input fields for a task
|
|
27
|
+
Returns:
|
|
28
|
+
list of input fields for a task
|
|
29
|
+
"""
|
|
30
|
+
return self._inputs
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def outputs(self) -> list[str]:
|
|
34
|
+
"""
|
|
35
|
+
Property describing output fields of the task
|
|
36
|
+
Returns:
|
|
37
|
+
list of outputs for a task
|
|
38
|
+
"""
|
|
39
|
+
return self._outputs
|
|
40
|
+
|
|
41
|
+
def format_input(self, input: dict[str, Any]) -> ChatFormat:
|
|
42
|
+
"""
|
|
43
|
+
Formats the input data for generating a question based on the provided "chunk".
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
input: A dictionary containing a single "chunk" key with the text input.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
The formatted chat object containing the input for query generation.
|
|
50
|
+
"""
|
|
51
|
+
chat = self._prompt_class(self._prompt_class.input_type(**input)).chat # type: ignore
|
|
52
|
+
return chat
|
|
53
|
+
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def format_output(self, output: str, input: dict[str, Any] | None = None) -> dict[str, str | list[str]]:
|
|
56
|
+
"""
|
|
57
|
+
Formats the generated question into a structured dictionary with the original "chunk" input.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
output: The generated question.
|
|
61
|
+
input: Optional; contains "chunk" key with the original input chunk.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
A dictionary containing "chunk" and "question".
|
|
65
|
+
"""
|
|
66
|
+
pass
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from distilabel.models import LLM
|
|
4
|
+
|
|
5
|
+
from ragbits.evaluate.dataset_generator.tasks.text_generation.base import BaseDistilabelTask
|
|
6
|
+
from ragbits.evaluate.dataset_generator.utils import get_closest_substring, get_passages_list
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class QueryGenTask(BaseDistilabelTask):
|
|
10
|
+
"""
|
|
11
|
+
A task for generating a question based on a provided text chunk.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, llm: LLM, prompt_class: str):
|
|
15
|
+
super().__init__(llm=llm, inputs=["chunk"], outputs=["question", "chunk"], prompt_class=prompt_class)
|
|
16
|
+
|
|
17
|
+
def format_output(self, output: str, input: dict[str, Any] | None = None) -> dict[str, str | list[str]]: # noqa: PLR6301
|
|
18
|
+
"""
|
|
19
|
+
Formats the generated question into a structured dictionary with the original "chunk" input.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
output: The generated question.
|
|
23
|
+
input: Optional; contains "chunk" key with the original input chunk.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
A dictionary containing "chunk" and "question".
|
|
27
|
+
"""
|
|
28
|
+
return {"chunk": input["chunk"], "question": output} # type: ignore
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class PassagesGenTask(BaseDistilabelTask):
|
|
32
|
+
"""
|
|
33
|
+
A task for generating passages related to a specific question and answer from a text chunk.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
should_get_matches: bool = False
|
|
37
|
+
|
|
38
|
+
def __init__(self, llm: LLM, prompt_class: str):
|
|
39
|
+
super().__init__(
|
|
40
|
+
llm=llm,
|
|
41
|
+
inputs=["chunk", "question", "basic_answer"],
|
|
42
|
+
outputs=["question", "chunk", "passages"],
|
|
43
|
+
prompt_class=prompt_class,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def format_output(self, output: str, input: dict[str, Any] | None = None) -> dict[str, str | list[str]]:
|
|
47
|
+
"""
|
|
48
|
+
Formats the model's output into a structured dictionary with "question", "chunk", and "passages".
|
|
49
|
+
If `get_matches` is `True`, attempts to find the closest matches for each passage within the
|
|
50
|
+
provided chunk.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
output: The raw output generated by the text generation model.
|
|
54
|
+
input: Required if `get_matches` is `True`, containing "chunk" and "question".
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
A dictionary with "chunk", "question", and a list of "passages".
|
|
58
|
+
"""
|
|
59
|
+
passages: list[str] = get_passages_list(output) or []
|
|
60
|
+
|
|
61
|
+
if self.should_get_matches:
|
|
62
|
+
matched_passages: list[str] = []
|
|
63
|
+
|
|
64
|
+
for passage in passages:
|
|
65
|
+
if passage in input["chunk"]: # type: ignore
|
|
66
|
+
matched_passages.append(passage)
|
|
67
|
+
else:
|
|
68
|
+
matched_passage = get_closest_substring(input["chunk"], passage) # type: ignore
|
|
69
|
+
matched_passages.append(matched_passage)
|
|
70
|
+
|
|
71
|
+
return {"chunk": input["chunk"], "question": input["question"], "passages": matched_passages} # type: ignore
|
|
72
|
+
|
|
73
|
+
return {"chunk": input["chunk"], "question": input["question"], "passages": passages} # type: ignore
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class AnswerGenTask(BaseDistilabelTask):
|
|
77
|
+
"""
|
|
78
|
+
A task for generating basic answers to questions based on a provided text chunk. This class extends
|
|
79
|
+
the `TextGeneration` task from the `distilabel` package.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self, llm: LLM, prompt_class: str):
|
|
83
|
+
super().__init__(llm=llm, inputs=["chunk", "question"], outputs=["basic_answer"], prompt_class=prompt_class)
|
|
84
|
+
|
|
85
|
+
def format_output(self, output: str, input: dict[str, Any] | None = None) -> dict[str, str | list[str]]: # noqa: PLR6301
|
|
86
|
+
"""
|
|
87
|
+
Formats the model's output into a structured dictionary with the "basic_answer" key.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
output: The raw output generated by the text generation model.
|
|
91
|
+
input: Optional; not typically used in this formatting.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
A dictionary with "basic_answer" as the key and the generated output as its value.
|
|
95
|
+
"""
|
|
96
|
+
return {"basic_answer": output}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import re
|
|
3
|
+
import warnings
|
|
4
|
+
from difflib import SequenceMatcher
|
|
5
|
+
from itertools import combinations
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_closest_substring(long: str, short: str) -> str:
|
|
9
|
+
"""
|
|
10
|
+
Finds the closest substring to short string in longer one
|
|
11
|
+
Args:
|
|
12
|
+
long: str - longer string
|
|
13
|
+
short: str - shorter string
|
|
14
|
+
Returns:
|
|
15
|
+
closest substring of longer
|
|
16
|
+
"""
|
|
17
|
+
a, b = max(
|
|
18
|
+
combinations(re.finditer("|".join(short.split()), long), 2),
|
|
19
|
+
key=lambda c: SequenceMatcher(None, long[c[0].start() : c[1].end()], short).ratio(),
|
|
20
|
+
)
|
|
21
|
+
return long[a.start() : b.end()]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_passages_list(raw_passages: str) -> list[str]:
|
|
25
|
+
"""
|
|
26
|
+
Formats LLM output to list of passages
|
|
27
|
+
Args:
|
|
28
|
+
raw_passages: string representing raw passages returned by llm
|
|
29
|
+
Returns:
|
|
30
|
+
list of parsed passages
|
|
31
|
+
"""
|
|
32
|
+
match = re.search(r"\[(.*?)\]", raw_passages, re.DOTALL)
|
|
33
|
+
|
|
34
|
+
if match:
|
|
35
|
+
passages_content = match.group(1)
|
|
36
|
+
try:
|
|
37
|
+
return json.loads("[" + passages_content + "]")
|
|
38
|
+
except (SyntaxError, ValueError):
|
|
39
|
+
warnings.warn("Unable to evaluate the passages content. Check the format.", category=UserWarning)
|
|
40
|
+
return []
|
|
41
|
+
else:
|
|
42
|
+
warnings.warn(message="No brackets found in the input string.", category=UserWarning)
|
|
43
|
+
return []
|