Singletons¶
Singletons are pre-built task templates that perform specific functions using a standardized structure. Each singleton is designed as a single instance that handles a specific task using Pydantic models for input validation and output formatting.
Factory¶
Dria’s Factory offers various ready-to-use Singletons for different scenarios, easily compatible with a DatasetGenerator
object.
For more specific needs, however, creating your own Singleton is recommended.
Here's a basic example of how to use a singleton. Code below uses the Simple
singleton from library which takes a prompt and executes it.
from dria import DriaDataset, DatasetGenerator, Model
from dria.factory import Simple
import asyncio
my_dataset = DriaDataset(
name="simple",
description="A simple dataset",
schema=Simple.OutputSchema,
)
generator = DatasetGenerator(dataset=my_dataset)
instructions = [
{
"prompt": "Write a haiku about open source AI."
},
]
asyncio.run(
generator.generate(
instructions=instructions,
singletons=Simple,
models=Model.LLAMA_3_1_8B_OR,
)
)
my_dataset.to_json()
Output is:
[
{
"prompt":"Write a haiku about open source AI.",
"generation":"Code for all to see free\nSharing wisdom, knowledge flows\nHumanity's gift back",
"model":"meta-llama\/llama-3.1-8b-instruct"
}
]
Writing Singletons¶
Dria's factory is limited, therefore writing a Singleton can adapt Dria Network to any problem at hand.
Basic Structure¶
A singleton consists of three main components:
- Input fields (using Pydantic Fields)
- Output schema (using Pydantic BaseModel)
- Workflow and callback methods
We can start by importing the necessary libraries and defining the input fields and output schema.
from dria.factory.utilities import get_abs_path
from dria.factory.workflows.template import SingletonTemplate
from dria.models import TaskResult
First step is to create a class that inherits from SingletonTemplate
and define the input fields.
class ValidatePrediction(SingletonTemplate):
# Input fields
prediction: str = Field(..., description="The predicted answer to be evaluated")
correct_answer: str = Field(
..., description="The correct answer to compare against"
)
SingletonTemplate
is a base class that provides the necessary functionality to create pre-built tasks.
Next step is to create the output schema.
class ValidationOutput(BaseModel):
prediction: str = Field(..., description="The prediction result.")
correct_answer: str = Field(..., description="The correct answer.")
validation: bool = Field(..., description="Validation result (True/False)")
model: str = Field(..., description="Model used for validation")
Output schema is attached to the singleton with built-in OutputSchema
attribute.
class ValidatePrediction(SingletonTemplate):
# Input fields
prediction: str = Field(..., description="The predicted answer to be evaluated")
correct_answer: str = Field(
..., description="The correct answer to compare against"
)
# Output schema
OutputSchema = ValidationOutput
Singleton class has two abstrat methods that need to be implemented: workflow
and callback
.
def workflow(self) -> Workflow
def callback(self, result: List[TaskResult]) -> List[ValidationOutput]
See workflows for more information on how to implement workflows.
The workflow
method defines the task to be executed and callback
method processes the result.
def workflow(self) -> Workflow:
"""
Generate a Task to determine if the predicted answer is contextually and semantically correct.
Returns:
Workflow: The constructed workflow
"""
# Initialize the workflow with variables
builder = WorkflowBuilder(
prediction=self.prediction, correct_answer=self.correct_answer
)
# Add a generative step using the prompt
builder.generative_step(
path=get_abs_path("validate.md"),
operator=Operator.GENERATION,
outputs=[Write.new("validation_result")],
)
# Define the flow of the workflow
flow = [Edge(source="0", target="_end")]
builder.flow(flow)
# Set the return value of the workflow
builder.set_return_value("validation_result")
return builder.build()
Since Dria supports multiple models, structured outputs are not forced. But can be added through schema
field of generative_step
.
If not, the format and parsing is up to the prompt you provided to the task.
This is the implemented prompt for Validator
You will be given a predicted answer to a question. Your task is to reason with your existing knowledge to evaluate if the predicted answer is correct or not.
Here is the predicted answer:
<prediction>
{{prediction}}
</prediction>
Here is the question answer:
<question>
{{correct_answer}}
</question>
To complete this task:
1. Carefully read both the prediction and the correct answer.
2. Compare the two answers, focusing on their semantic meaning and contextual relevance.
3. Determine if the predicted answer conveys the same core information and is contextually appropriate, even if the wording is different.
4. Ignore minor differences in phrasing, word choice, or additional details as long as the main point is correct.
Output your decision as follows:
- If the predicted answer is contextually and semantically correct, output only the word "true" (without quotes).
- If the predicted answer is not contextually or semantically correct, output only the word "false" (without quotes).
Do not provide any explanation or justification for your decision. Your entire response should consist of a single word: either "true" or "false".
Based on this prompt, we add a callback
method:
def callback(self, result: List[TaskResult]) -> List[ValidationOutput]:
"""
Parse the results into validated ValidationOutput objects
Args:
result: List of TaskResult objects
Returns:
List[ValidationOutput]: List of validated outputs
"""
outputs = []
for r in result:
if r.result.lower() == "true":
outputs.append(
ValidationOutput(
prediction=self.prediction,
correct_answer=self.correct_answer,
validation=True,
model=r.model,
)
)
elif r.result.lower() == "false":
outputs.append(ValidationOutput(validation=False, model=r.model))
else:
raise ValueError("The result is not a boolean value.")
return outputs