【AI for Science試行録】第5回 Biomniソースコード検証 Part3

バイオメディカル分野向けAIエージェントのBiomniでは、独自に作成したベンチマーク用データセットを取り込み、性能を定量的に評価できる仕組みが用意されています。この機能を使うことで、自分の研究分野におけるBiomniの性能を数値として示すことが可能です。今回は、その仕組みを実際に試した結果を報告します。

定量評価の重要性

AIエージェントは、従来のコンピュータワークフローとは異なり、大規模言語モデル(LLM)を介して複雑なタスクを自律的に実行できます。しかしその一方で、常に予期せぬエラーや不安定な挙動が起こる恐れがあります。ユーザーの期待する通りに安定して動作させることができるかどうかは、大きな課題です。

このような課題へのアプローチとして、定量評価による検証が非常に重要と考えます。Biomniには、この定量評価を研究者ごとに柔軟に実施できる仕組みが組み込まれています。

評価データの形式

評価に使うデータの形式は特に制限されません。ただし、独自形式を用いる場合は、それに対応する評価関数を自前で実装する必要があります。一方で、Biomniが標準で利用する形式に合わせれば、そのまま評価関数を活用できます。

標準形式は、エージェント開発の非営利団体として知られるFutureHouseが開発したベンチマークLab-Benchが用いられており、JSONL形式の四択問題です。以下は試験的に作成した7件の問題を収録したtest_evaldata.jsonの例です。

# test_evaldata.json
{"id": "1", "tag": "custom", "version": "1.0", "question": "2014-2016年のPubMed収載Journal articleの数は?", "ideal": "20823", "distractors": ["18908", "0", "20888"]}
{"id": "2", "tag": "custom", "version": "1.0", "question": "PubMedにおいて、2016年1月5日から2016年1月20日の間のPMC open access subsetに入っているreview論文は何件あるか?", "ideal": "2", "distractors": ["0", "Not found", "25"]}
{"id": "3", "tag": "custom", "version": "1.0", "question": "/app/data/2014-2016_articles_30.txtのPMIDsの数は?", "ideal": "30", "distractors": ["0", "Not found", "24"]}
{"id": "4", "tag": "custom", "version": "1.0", "question": "/app/data/2014-2016_pubtator_30.csvの中からヒトを対象とした研究文献を除外すると、何件の文献が残るか?", "ideal": "14", "distractors": ["10", "0", "20"]}
{"id": "5", "tag": "custom", "version": "1.0", "question": "PMIDが27093237の文献においてPubTatorで最もアノテーションされている生物種は?", "ideal": "Methanobacteriales", "distractors": ["swine", "human", "Metanosarcinales"]}
{"id": "6", "tag": "custom", "version": "1.0", "question": "PubMedにおいて、2021年12月21日から2021年12月31日におけるPMC open access subsetに入っているJournal Articleの数は?", "ideal": "180", "distractors": ["0", "165", "16"]}
{"id": "7", "tag": "custom", "version": "1.0", "question": "/app/data/pmids.txtの中で、タイトルまたはアブストラクトに遺伝子名が記述されている文献は何件あるか?", "ideal": "2", "distractors": ["0", "1", "4"]}

評価用クラスの実装

Biomniのlab_benchモジュールを参考に、biomni/task/custom_benchmark.pyを作成しました。コードの詳細は割愛しますが、このクラスは、

  • JSONLファイルを読み込み
  • 選択肢をシャッフル
  • 正解を記録
  • プロンプトの整形

といった処理を行い、最終的にエージェントの回答と正解を比較して正解率を算出する簡易版評価コードです。

import pandas as pd  
import numpy as np  
import json  
from biomni.task.base_task import base_task  
  
class CustomBenchmark(base_task):  
    """
    Multiple-choice question benchmark for biology tasks.
    This class loads a JSONL dataset, converts it to a Lab-Bench style prompt,
    and provides iteration, evaluation, and structured output validation.
    """
    def __init__(self, jsonl_path="./data/test_evaldata.jsonl"):  
        # JSONLファイルを読み込み  
        data = []  
        with open(jsonl_path, 'r', encoding='utf-8') as f:  
            for line in f:  
                data.append(json.loads(line.strip()))  
          
        # Lab-Benchと同じプロンプト形式を使用  
        self.prompt = """
The following is a multiple choice question about biology.  
Please answer by responding with the letter of the correct answer.  
  
Question: {question}  
Options:  
{options}  
  
You MUST include the letter of the correct answer within the following tags:  
[ANSWER] and [/ANSWER]. For example, '[ANSWER]<answer>[/ANSWER]',  
where <answer> is the correct letter. Always answer in exactly this format  
of a single letter between the two tags, even if you are unsure.  
We require this because we use automatic parsing.  
        """  
          
        # データを処理してLab-Benchと同じ形式に変換  
        np.random.seed(42)  
        processed_data = []  
          
        for item in data:  
            # 選択肢を作成(ideal + distractors)  
            options = [item['ideal']] + item['distractors']  
            np.random.shuffle(options)  
              
            # 選択肢に文字ラベル(A, B, C, D)を付与  
            options_letters = "\n".join([chr(ord("A") + i) + "." + opt for i, opt in enumerate(options)])  
              
            # 正解の文字ラベルを計算  
            correct_index = options.index(item['ideal'])  
            letter_answer = chr(ord("A") + correct_index)  
              
            processed_data.append({  
                'id': item['id'],  
                'question': item['question'],  
                'options': options,  
                'options_letters': options_letters,  
                'letter_answer': letter_answer,  
                'ideal': item['ideal']  
            })  
          
        # データを配列として保存  
        self.queries = [item['question'] for item in processed_data]  
        self.options = [item['options_letters'] for item in processed_data]  
        self.answers = [item['letter_answer'] for item in processed_data]  
        self.ids = [item['id'] for item in processed_data]  
      
    def __len__(self):  
        return len(self.queries)  
      
    def get_example(self, index=None):  
        if index is None:  
            index = np.random.randint(len(self.queries))  
          
        return {  
            "prompt": self.prompt.format(  
                question=self.queries[index],   
                options=self.options[index]  
            ),  
            "answer": self.answers[index],  
        }  
      
    def get_iterator(self):  
        for i in range(len(self.queries)):  
            yield self.get_example(i)  
      
    def evaluate(self, responses):  
        from sklearn.metrics import accuracy_score  
          
        ground_truth = self.answers 
        responses = np.array(responses)  
          
        return {  
            "accuracy": accuracy_score(ground_truth, responses),  
        }  
      
    def output_class(self):  
        from pydantic import BaseModel, Field  
          
        class MultipleChoiceOutput(BaseModel):  
            choice: str = Field(description="Multiple choice answer (A, B, C, or D)")  
          
        return MultipleChoiceOutput

評価の流れ

  1. 独自のベンチマークデータを読み込み
  2. Biomniエージェント(A1)を起動
  3. 問題を1問ずつ提示し、回答を収集
  4. 正解と比較し正答率を計算
  5. 結果をJSON/CSVに保存

下記コードにより定量評価を実行しています。agentを起動し、作成したベンチマーククラスを呼び出します。1問ずつエージェントに問題を解かせ、結果をリストに格納し、最後に評価を実施します。

# 定量評価の実行コード

import json
import re
from datetime import datetime
import os

import pandas as pd
import pytz

from biomni.agent import A1
from biomni.task.lab_bench import lab_bench

from biomni.task.custom_benchmark import CustomBenchmark  # 作成したベンチマーク
# 回答抽出ロジックを定義
def extract_answer_from_result(result):
    """Lab-Benchと同じ回答抽出ロジック"""
    answer_match = re.search(r"\[ANSWER\]([A-Z])\[/ANSWER\]", result)
    if answer_match:
        return answer_match.group(1)
    else:
        return "X"  # 回答が見つからない場合のデフォルト

# A1エージェントの初期化
agent = A1(path="/app/data", llm="gpt-4.1-mini-2025-04-14")

# ベンチマークデータの読み込み
benchmark = CustomBenchmark(jsonl_path="./data/test_evaldata.jsonl")

responses = []
detailed_results = []
print(f"カスタムベンチマーク開始: {len(benchmark)}問")

# 1問ずつエージェントを実行
for i, example in enumerate(benchmark.get_iterator()):
    print(f"問題 {i + 1}/{len(benchmark)} を処理中...")

    log_messages, result, token_stats = agent.go(example["prompt"])

    response = extract_answer_from_result(result)
    responses.append(response)

    # 詳細結果を保存
    detailed_results.append(
        {
            "question_id": benchmark.ids[i],
            "question": benchmark.query[i],
            "options": benchmark.options[i],
            "correct_answer": benchmark.answer[i],
            "agent_response": response,
            "full_agent_output": result,
            "is_correct": response == benchmark.answer[i],
        }
    )
    
# 評価の実行
evaluation_results = benchmark.evaluate(responses)

下記は、もし実行ログを設定した場合のイメージです。

# 実行ログのイメージ
カスタムベンチマーク開始: 7問
問題 1/7 を処理中...
問題 2/7 を処理中...
...
=== 評価結果 ===
正答率: 0.428

実験結果

簡素構成のBiomni(ツール・データを最小限に絞った環境)で7問のベンチマークを3回実行したところ、平均正答率は42.8%となりました。これは50%を下回り、必ずしも良好な結果ではありません。過去の試行録でも触れてきたように、

  • PubMedにおける精緻な検索
  • 文献に付与された遺伝子名・生物種名注釈を用いたフィルタリング

といったタスクは、現時点のBiomniではまだ難しい領域であることが示唆されます。

まとめ

Biomniは、定量評価を比較的容易に導入できる環境を提供してくれており、非常に有用であると感じました。エージェントは小さな変更でも挙動が大きく変わるため、ベンチマーク評価システムを用いることで、調整が全体システムに与える影響を定量的に把握できます。

今後、評価データを拡充しつつ検証を続けることで、Biomniの強み・弱みをさらに明確にできると期待されます。