Nanny7's picture
feat: initial commit for B Map AI backend and Gemma LLM service
2f86d9b
Raw History Blame Contribute Delete
6.48 kB
# Copyright 2026 DeepMind Technologies Limited.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unit tests for PubMedQA evaluation utilities."""
import dataclasses
from absl.testing import absltest
from absl.testing import parameterized
from gemma.diffusion.hackable_diffusion_adapter.eval import pubmedqa_eval
import jax.numpy as jnp
class ExtractPubMedQAAnswerTest(parameterized.TestCase):
@parameterized.parameters(
# Explicit "The answer is:" marker variants.
("The answer is: yes", "yes"),
("The answer is: no", "no"),
("The answer is: maybe", "maybe"),
("The answer is:yes", "yes"), # No space after colon.
("The answer is yes", "yes"), # Without colon.
(
"Based on the evidence, the study shows improvement."
+ " The answer is: yes",
"yes",
),
(
"The results are inconclusive. The answer is: maybe",
"maybe",
),
)
def test_extraction(self, text, expected):
self.assertEqual(pubmedqa_eval.extract_pubmedqa_answer(text), expected)
@parameterized.parameters(
"",
"The study was inconclusive.",
"42",
# No fallback heuristics — bare answers without the marker return None.
"yes",
"Yes",
"no",
"maybe",
"No, the study does not support this.",
"Maybe, further studies are needed.",
# Previously these would incorrectly match via heuristics.
"Based on the results, no significant effect was found.",
"The study showed novel approaches to treatment.",
"Yesterday was a good day.", # "yes" substring in "yesterday".
)
def test_extraction_returns_none(self, text):
self.assertIsNone(pubmedqa_eval.extract_pubmedqa_answer(text))
class ScorePubMedQATest(parameterized.TestCase):
@parameterized.parameters(
("The answer is: yes", "yes", True),
("The answer is: no", "no", True),
("The answer is: maybe", "maybe", True),
("The answer is: yes", "no", False),
("The answer is: no", "maybe", False),
# Without marker — no match even if the answer word appears.
("yes", "yes", False),
("no", "no", False),
("The study is inconclusive.", "yes", False),
("Based on the results, no significant effect was found.", "no", False),
)
def test_scoring(self, generated, ground_truth, expected):
self.assertEqual(
pubmedqa_eval.score_pubmedqa(generated, ground_truth), expected
)
class MockTokenizer:
def decode(self, tokens: list[int]) -> str:
# 1 maps to "The answer is: yes", 2 to "yes",
# 3 to "The answer is: no", 4 to "no"
mapping = {
1: "The answer is: yes",
2: "yes",
3: "The answer is: no",
4: "no",
}
return " ".join(mapping.get(t, "") for t in tokens if t > 0).strip()
class PubMedQAAccuracyTest(parameterized.TestCase):
def test_pubmedqa_accuracy(self):
@dataclasses.dataclass(kw_only=True, frozen=True)
class TestPubMedQAAccuracy(pubmedqa_eval.PubMedQAAccuracy):
@property
def tokenizer(self) -> MockTokenizer:
return MockTokenizer()
metric = TestPubMedQAAccuracy(
tokens="samples",
ground_truth="batch.short_answer_tokens",
)
tokens = jnp.array([
[1, -1], # "The answer is: yes"
[3, -1], # "The answer is: no"
])
ground_truth = jnp.array([
[2, -1], # "yes"
[2, -1], # "yes"
])
state = metric.get_state(tokens=tokens, ground_truth=ground_truth)
accuracy = state.compute()
self.assertEqual(accuracy, 0.5)
class ScoreBleuTest(parameterized.TestCase):
"""Tests for score_bleu."""
def test_perfect_match(self):
"""Identical strings should yield 100."""
score = pubmedqa_eval.score_bleu("Bonjour le monde", "Bonjour le monde")
self.assertAlmostEqual(score, 100.0, places=1)
def test_wrong_text(self):
"""Completely wrong text should yield low BLEU."""
score = pubmedqa_eval.score_bleu("This is wrong", "Bonjour le monde")
self.assertLess(score, 10.0)
def test_empty_generated(self):
score = pubmedqa_eval.score_bleu("", "Bonjour")
self.assertEqual(score, 0.0)
def test_empty_ground_truth(self):
score = pubmedqa_eval.score_bleu("Bonjour", "")
self.assertEqual(score, 0.0)
def test_both_empty(self):
score = pubmedqa_eval.score_bleu("", "")
self.assertEqual(score, 0.0)
def test_partial_match(self):
gt = "Le chat dort sur le tapis rouge dans la grande maison blanche"
partial = "Le chat mange sur le tapis rouge dans la petite maison blanche"
score = pubmedqa_eval.score_bleu(partial, gt)
self.assertGreater(score, 0.0)
self.assertLess(score, 100.0)
def test_whitespace_handling(self):
score = pubmedqa_eval.score_bleu(" Bonjour ", " Bonjour ")
self.assertAlmostEqual(score, 100.0, places=1)
class BLEUScoreMetricTest(absltest.TestCase):
def test_tokenizer_path_default(self):
metric = pubmedqa_eval.BLEUScore(
tokens="samples",
ground_truth="batch.response_tokens",
)
self.assertIn("gemma4", metric.tokenizer_path)
def test_bleu_round_trip(self):
class MockBLEUTokenizer:
def decode(self, tokens: list[int]) -> str:
mapping = {1: "Bonjour le monde", 2: "Bonjour le monde"}
return " ".join(mapping.get(t, "") for t in tokens if t > 0).strip()
@dataclasses.dataclass(kw_only=True, frozen=True)
class TestBLEUScore(pubmedqa_eval.BLEUScore):
@property
def tokenizer(self) -> MockBLEUTokenizer:
return MockBLEUTokenizer()
metric = TestBLEUScore(
tokens="samples",
ground_truth="batch.response_tokens",
)
tokens = jnp.array([[1, -1]])
ground_truth = jnp.array([[2, -1]])
state = metric.get_state(tokens=tokens, ground_truth=ground_truth)
score = state.compute()
self.assertAlmostEqual(score, 100.0, places=1)
if __name__ == "__main__":
absltest.main()