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.
Files changed (59) hide show
  1. ragbits/evaluate/__init__.py +0 -0
  2. ragbits/evaluate/agent_simulation/__init__.py +87 -0
  3. ragbits/evaluate/agent_simulation/context.py +140 -0
  4. ragbits/evaluate/agent_simulation/conversation.py +333 -0
  5. ragbits/evaluate/agent_simulation/deepeval_evaluator.py +92 -0
  6. ragbits/evaluate/agent_simulation/logger.py +165 -0
  7. ragbits/evaluate/agent_simulation/metrics/__init__.py +19 -0
  8. ragbits/evaluate/agent_simulation/metrics/builtin.py +221 -0
  9. ragbits/evaluate/agent_simulation/metrics/collectors.py +142 -0
  10. ragbits/evaluate/agent_simulation/models.py +37 -0
  11. ragbits/evaluate/agent_simulation/results.py +200 -0
  12. ragbits/evaluate/agent_simulation/scenarios.py +129 -0
  13. ragbits/evaluate/agent_simulation/simulation.py +245 -0
  14. ragbits/evaluate/cli.py +150 -0
  15. ragbits/evaluate/config.py +11 -0
  16. ragbits/evaluate/dataloaders/__init__.py +3 -0
  17. ragbits/evaluate/dataloaders/base.py +95 -0
  18. ragbits/evaluate/dataloaders/document_search.py +61 -0
  19. ragbits/evaluate/dataloaders/exceptions.py +25 -0
  20. ragbits/evaluate/dataloaders/gaia.py +78 -0
  21. ragbits/evaluate/dataloaders/hotpot_qa.py +95 -0
  22. ragbits/evaluate/dataloaders/human_eval.py +70 -0
  23. ragbits/evaluate/dataloaders/question_answer.py +56 -0
  24. ragbits/evaluate/dataset_generator/__init__.py +0 -0
  25. ragbits/evaluate/dataset_generator/pipeline.py +141 -0
  26. ragbits/evaluate/dataset_generator/prompts/__init__.py +0 -0
  27. ragbits/evaluate/dataset_generator/prompts/corpus_generation.py +21 -0
  28. ragbits/evaluate/dataset_generator/prompts/qa.py +83 -0
  29. ragbits/evaluate/dataset_generator/tasks/__init__.py +0 -0
  30. ragbits/evaluate/dataset_generator/tasks/corpus_generation.py +67 -0
  31. ragbits/evaluate/dataset_generator/tasks/filter/__init__.py +0 -0
  32. ragbits/evaluate/dataset_generator/tasks/filter/base.py +43 -0
  33. ragbits/evaluate/dataset_generator/tasks/filter/dont_know.py +34 -0
  34. ragbits/evaluate/dataset_generator/tasks/text_generation/__init__.py +0 -0
  35. ragbits/evaluate/dataset_generator/tasks/text_generation/base.py +66 -0
  36. ragbits/evaluate/dataset_generator/tasks/text_generation/qa.py +96 -0
  37. ragbits/evaluate/dataset_generator/utils.py +43 -0
  38. ragbits/evaluate/evaluator.py +244 -0
  39. ragbits/evaluate/factories/__init__.py +42 -0
  40. ragbits/evaluate/metrics/__init__.py +3 -0
  41. ragbits/evaluate/metrics/base.py +89 -0
  42. ragbits/evaluate/metrics/document_search.py +90 -0
  43. ragbits/evaluate/metrics/gaia.py +84 -0
  44. ragbits/evaluate/metrics/hotpot_qa.py +51 -0
  45. ragbits/evaluate/metrics/human_eval.py +105 -0
  46. ragbits/evaluate/metrics/question_answer.py +205 -0
  47. ragbits/evaluate/optimizer.py +210 -0
  48. ragbits/evaluate/pipelines/__init__.py +37 -0
  49. ragbits/evaluate/pipelines/base.py +64 -0
  50. ragbits/evaluate/pipelines/document_search.py +106 -0
  51. ragbits/evaluate/pipelines/gaia.py +249 -0
  52. ragbits/evaluate/pipelines/hotpot_qa.py +342 -0
  53. ragbits/evaluate/pipelines/human_eval.py +323 -0
  54. ragbits/evaluate/pipelines/question_answer.py +96 -0
  55. ragbits/evaluate/py.typed +0 -0
  56. ragbits/evaluate/utils.py +160 -0
  57. ragbits_evaluate-0.0.30.dev29302392.dist-info/METADATA +58 -0
  58. ragbits_evaluate-0.0.30.dev29302392.dist-info/RECORD +59 -0
  59. 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
@@ -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)
@@ -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 []