RAGASragas 0.4.3 · Python 3.9+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
20 small wins to finish your pathNext lesson

InstructorBaseRagasLLM: the stand-in judge

Every metric from here on asks a model questions. This lesson writes the model, so the rest of the course needs no key and no network.

A RAGAS judge has one method that matters: generate(prompt, response_model). The prompt is text, the response model is a Pydantic class, and the judge must return an instance of it. Anything that does that and inherits InstructorBaseRagasLLM is a model as far as RAGAS is concerned.

Reading the prompt

RAGAS prompts are long, but they all end the same way: the example section, then the real input as JSON. So the judge does not need to understand English, only to find that block.

python
def asked(prompt):
    """The JSON block a RAGAS prompt ends with: the input it wants judged.

    A metric you wrote yourself has no such block, so the prompt itself is the
    text to judge."""
    block = prompt.rsplit("input:", 1)[-1].rsplit("Output:", 1)[0].strip()
    try:
        return json.loads(block)
    except ValueError:
        return {"response": prompt}

Then two helpers for deciding things. The first keeps the words that carry meaning.

python
def words(text):
    """The words that carry meaning, lower case, without the common ones."""
    return {w for w in re.findall(r"[a-z0-9]+", str(text).lower()) if len(w) > 2 and w not in SKIP}

The second is the rule almost every answer below is built on: a claim counts as supported when every word that matters in it appears in the context.

python
def supported(claim, context):
    """True when every word that matters in the claim is also in the context."""
    need = words(claim)
    return bool(need) and need <= words(context)

One answer per question

A metric does not ask one big question. It asks several small ones, each with its own response model, so the judge keeps one answer per model name in a dictionary.

python
ANSWERS = {}


def answers(name):
    """Register one answer, under the name of the model RAGAS asks for."""
    def keep(answer):
        ANSWERS[name] = answer
        return answer
    return keep

The judge

python
class PretendJudge(InstructorBaseRagasLLM):
    def generate(self, prompt, response_model):
        name = response_model.__name__
        if name not in ANSWERS:
            raise NotImplementedError(f"The pretend judge has no answer for {name} yet")
        return response_model(**ANSWERS[name](asked(prompt), response_model))

    async def agenerate(self, prompt, response_model):
        return self.generate(prompt, response_model)

That is the whole class. It looks up the answer for the model it was handed, calls it with the prompt's input block, and builds the object. A question with no answer raises, naming itself, which is how you find out what a new metric wants.

Now the first answer. Faithfulness opens by asking for the statements in an answer, and splitting on full stops is close enough for a stand-in.

python
@answers("StatementGeneratorOutput")
def split_the_answer(data, model):
    return {"statements": sentences(data.get("answer", ""))}
Example
from pretend_ragas import PretendJudge
from ragas.metrics.collections.faithfulness.util import StatementGeneratorOutput

judge = PretendJudge()
prompt = """Now perform the same with the following input
input: {
    "question": "Where is order A17?",
    "answer": "Order A17 shipped on 3 March. It was sent by courier."
}
Output: """
print(judge.generate(prompt, StatementGeneratorOutput).statements)

That is the first thing faithfulness asks: break this answer into statements. Two sentences in, two statements out, and no model involved.

Example
from ragas.metrics.collections.answer_relevancy.util import AnswerRelevanceOutput
from pydantic import BaseModel


class Whatever(BaseModel):
    verdict: int


try:
    judge.generate("input: {}\nOutput: ", Whatever)
except NotImplementedError as error:
    print(error)

And a question it cannot answer. Each of the next five lessons adds one branch and explains the rule behind it.

A real judge does the same job
A real model gets the same prompt and returns the same Pydantic object, filled in by reading rather than by counting words. Lesson 17 swaps this one for GPT, and nothing else changes.
Try it yourself
  • Give the demo prompt an answer with three sentences and print the statements.
  • Add "march" to SKIP, then re-run lesson 5 and see which score moves.

This is what real progress feels like.