> ## Documentation Index
> Fetch the complete documentation index at: https://wb-21fd5541-run-filter-ui-updates.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# RAG アプリケーションを評価する

> Weave と LLM judge を使用して、RAG アプリケーションを構築・評価します

export const GitHubLink = ({url}) => <a href={url} target="_blank" rel="noopener noreferrer" className="github-source-link">
    <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
      <path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
    </svg>
    GitHub のソース
  </a>;

export const ColabLink = ({url}) => <a href={url} target="_blank" rel="noopener noreferrer" className="colab-link">
    <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
      <path d="M14.25.18l.9.2.73.26.59.3.45.32.34.34.25.34.16.33.1.3.04.26.02.2-.01.13V8.5l-.05.63-.13.55-.21.46-.26.38-.3.31-.33.25-.35.19-.35.14-.33.1-.3.07-.26.04-.21.02H8.77l-.69.05-.59.14-.5.22-.41.27-.33.32-.27.35-.2.36-.15.37-.1.35-.07.32-.04.27-.02.21v3.06H3.17l-.21-.03-.28-.07-.32-.12-.35-.18-.36-.26-.36-.36-.35-.46-.32-.59-.28-.73-.21-.88-.14-1.05-.05-1.23.06-1.22.16-1.04.24-.87.32-.71.36-.57.4-.44.42-.33.42-.24.4-.16.36-.1.32-.05.24-.01h.16l.06.01h8.16v-.83H6.18l-.01-2.75-.02-.37.05-.34.11-.31.17-.28.25-.26.31-.23.38-.2.44-.18.51-.15.58-.12.64-.1.71-.06.77-.04.84-.02 1.27.05zm-6.3 1.98l-.23.33-.08.41.08.41.23.34.33.22.41.09.41-.09.33-.22.23-.34.08-.41-.08-.41-.23-.33-.33-.22-.41-.09-.41.09zm13.09 3.95l.28.06.32.12.35.18.36.27.36.35.35.47.32.59.28.73.21.88.14 1.04.05 1.23-.06 1.23-.16 1.04-.24.86-.32.71-.36.57-.4.45-.42.33-.42.24-.4.16-.36.09-.32.05-.24.02-.16-.01h-8.22v.82h5.84l.01 2.76.02.36-.05.34-.11.31-.17.29-.25.25-.31.24-.38.2-.44.17-.51.15-.58.13-.64.09-.71.07-.77.04-.84.01-1.27-.04-1.07-.14-.9-.2-.73-.25-.59-.3-.45-.33-.34-.34-.25-.34-.16-.33-.1-.3-.04-.25-.02-.2.01-.13v-5.34l.05-.64.13-.54.21-.46.26-.38.3-.32.33-.24.35-.2.35-.14.33-.1.3-.06.26-.04.21-.02.13-.01h5.84l.69-.05.59-.14.5-.21.41-.28.33-.32.27-.35.2-.36.15-.36.1-.35.07-.32.04-.28.02-.21V6.07h2.09l.14.01.21.03zm-6.47 14.25l-.23.33-.08.41.08.41.23.33.33.23.41.08.41-.08.33-.23.23-.33.08-.41-.08-.41-.23-.33-.33-.23-.41-.08-.41.08z" />
    </svg>
    Colabで試す
  </a>;

<div style={{ display: 'flex', gap: '12px', flexWrap: 'wrap' }}>
  <ColabLink url="https://colab.research.google.com/github/wandb/docs/blob/main/weave/cookbooks/source/evaluate_rag_applications.ipynb" />

  <GitHubLink url="https://github.com/wandb/docs/blob/main/weave/cookbooks/source/evaluate_rag_applications.ipynb" />
</div>

Retrieval Augmented Generation (RAG) は、独自のナレッジベースを利用できる生成 AI アプリを構築する一般的な手法です。

<img src="https://mintcdn.com/wb-21fd5541-run-filter-ui-updates/XwEKrQdeBQTkOa9I/images/evals-hero.png?fit=max&auto=format&n=XwEKrQdeBQTkOa9I&q=85&s=f91c682a230388ceab95bd0e3c21365f" alt="Evals hero" width="4100" height="2160" data-path="images/evals-hero.png" />

<div id="what-youll-learn">
  ## 学べること:
</div>

このガイドでは、次の方法を学びます。

* ナレッジベースを構築する
* 関連するドキュメントを検索する 取得 step を含む RAG アプリケーションを作成する
* Weave で取得 step をトラッキングする
* LLM judge を使って RAG アプリケーションを評価し、コンテキストの適合率を測定する
* カスタムのスコアリング関数を定義する

<div id="prerequisites">
  ## 前提条件
</div>

* [W\&Bアカウント](https://wandb.ai/signup)
* Python 3.10+ または Node.js 18+
* 必要なパッケージがインストールされていること:
  * **Python**: `pip install weave openai`
  * **TypeScript**: `npm install weave openai`
* [OpenAI APIキー](https://platform.openai.com/api-keys) が環境変数として設定されていること

<div id="build-a-knowledge-base">
  ## ナレッジベースを構築する
</div>

まず、記事の埋め込みを計算します。通常、この処理は記事ごとに一度だけ行い、埋め込みとメタデータをデータベースに保存しますが、ここでは簡単にするため、スクリプトを実行するたびに毎回計算しています。

<Tabs>
  <Tab title="Python">
    ```python lines theme={null}
    from openai import OpenAI
    import weave
    from weave import Model
    import numpy as np
    import json
    import asyncio

    articles = [
        "Novo Nordisk and Eli Lilly rival soars 32 percent after promising weight loss drug results Shares of Denmarks Zealand Pharma shot 32 percent higher in morning trade, after results showed success in its liver disease treatment survodutide, which is also on trial as a drug to treat obesity. The trial “tells us that the 6mg dose is safe, which is the top dose used in the ongoing [Phase 3] obesity trial too,” one analyst said in a note. The results come amid feverish investor interest in drugs that can be used for weight loss.",
        "Berkshire shares jump after big profit gain as Buffetts conglomerate nears $1 trillion valuation Berkshire Hathaway shares rose on Monday after Warren Buffetts conglomerate posted strong earnings for the fourth quarter over the weekend. Berkshires Class A and B shares jumped more than 1.5%, each. Class A shares are higher by more than 17% this year, while Class B has gained more than 18%. Berkshire was last valued at $930.1 billion, up from $905.5 billion where it closed on Friday, according to FactSet. Berkshire on Saturday posted fourth-quarter operating earnings of $8.481 billion, about 28 percent higher than the $6.625 billion from the year-ago period, driven by big gains in its insurance business. Operating earnings refers to profits from businesses across insurance, railroads and utilities. Meanwhile, Berkshires cash levels also swelled to record levels. The conglomerate held $167.6 billion in cash in the fourth quarter, surpassing the $157.2 billion record the conglomerate held in the prior quarter.",
        "Highmark Health says its combining tech from Google and Epic to give doctors easier access to information Highmark Health announced it is integrating technology from Google Cloud and the health-care software company Epic Systems. The integration aims to make it easier for both payers and providers to access key information they need, even if its stored across multiple points and formats, the company said. Highmark is the parent company of a health plan with 7 million members, a provider network of 14 hospitals and other entities",
        "Rivian and Lucid shares plunge after weak EV earnings reports Shares of electric vehicle makers Rivian and Lucid fell Thursday after the companies reported stagnant production in their fourth-quarter earnings after the bell Wednesday. Rivian shares sank about 25 percent, and Lucids stock dropped around 17 percent. Rivian forecast it will make 57,000 vehicles in 2024, slightly less than the 57,232 vehicles it produced in 2023. Lucid said it expects to make 9,000 vehicles in 2024, more than the 8,428 vehicles it made in 2023.",
        "Mauritius blocks Norwegian cruise ship over fears of a potential cholera outbreak Local authorities on Sunday denied permission for the Norwegian Dawn ship, which has 2,184 passengers and 1,026 crew on board, to access the Mauritius capital of Port Louis, citing “potential health risks.” The Mauritius Ports Authority said Sunday that samples were taken from at least 15 passengers on board the cruise ship. A spokesperson for the U.S.-headquartered Norwegian Cruise Line Holdings said Sunday that 'a small number of guests experienced mild symptoms of a stomach-related illness' during Norwegian Dawns South Africa voyage.",
        "Intuitive Machines lands on the moon in historic first for a U.S. company Intuitive Machines Nova-C cargo lander, named Odysseus after the mythological Greek hero, is the first U.S. spacecraft to soft land on the lunar surface since 1972. Intuitive Machines is the first company to pull off a moon landing — government agencies have carried out all previously successful missions. The company's stock surged in extended trading Thursday, after falling 11 percent in regular trading.",
        "Lunar landing photos: Intuitive Machines Odysseus sends back first images from the moon Intuitive Machines cargo moon lander Odysseus returned its first images from the surface. Company executives believe the lander caught its landing gear sideways on the moon's surface while touching down and tipped over. Despite resting on its side, the company's historic IM-1 mission is still operating on the moon.",
    ]

    def docs_to_embeddings(docs: list) -> list:
        openai = OpenAI()
        document_embeddings = []
        for doc in docs:
            response = (
                openai.embeddings.create(input=doc, model="text-embedding-3-small")
                .data[0]
                .embedding
            )
            document_embeddings.append(response)
        return document_embeddings

    article_embeddings = docs_to_embeddings(articles) # 注意: 通常はこの処理を一度だけ実行し、埋め込みとメタデータをデータベースに格納します
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript lines theme={null}
    require('dotenv').config();
    import { OpenAI } from 'openai';
    import * as weave from 'weave';

    interface Article {
        text: string;
        embedding?: number[];
    }

    const articles: Article[] = [
        { 
            text: `Novo Nordisk and Eli Lilly rival soars 32 percent after promising weight loss drug results Shares of Denmarks Zealand Pharma shot 32 percent higher in morning trade, after results showed success in its liver disease treatment survodutide, which is also on trial as a drug to treat obesity. The trial tells us that the 6mg dose is safe, which is the top dose used in the ongoing [Phase 3] obesity trial too, one analyst said in a note. The results come amid feverish investor interest in drugs that can be used for weight loss.`
        },
        { 
            text: `Berkshire shares jump after big profit gain as Buffetts conglomerate nears $1 trillion valuation Berkshire Hathaway shares rose on Monday after Warren Buffetts conglomerate posted strong earnings for the fourth quarter over the weekend. Berkshires Class A and B shares jumped more than 1.5%, each. Class A shares are higher by more than 17% this year, while Class B has gained more than 18%. Berkshire was last valued at $930.1 billion, up from $905.5 billion where it closed on Friday, according to FactSet. Berkshire on Saturday posted fourth-quarter operating earnings of $8.481 billion, about 28 percent higher than the $6.625 billion from the year-ago period, driven by big gains in its insurance business. Operating earnings refers to profits from businesses across insurance, railroads and utilities. Meanwhile, Berkshires cash levels also swelled to record levels. The conglomerate held $167.6 billion in cash in the fourth quarter, surpassing the $157.2 billion record the conglomerate held in the prior quarter.`
        },
        { 
            text: `Highmark Health says its combining tech from Google and Epic to give doctors easier access to information Highmark Health announced it is integrating technology from Google Cloud and the health-care software company Epic Systems. The integration aims to make it easier for both payers and providers to access key information they need, even if its stored across multiple points and formats, the company said. Highmark is the parent company of a health plan with 7 million members, a provider network of 14 hospitals and other entities`
        }
    ];

    function cosineSimilarity(a: number[], b: number[]): number {
        const dotProduct = a.reduce((sum, val, i) => sum + val * b[i], 0);
        const magnitudeA = Math.sqrt(a.reduce((sum, val) => sum + val * val, 0));
        const magnitudeB = Math.sqrt(b.reduce((sum, val) => sum + val * val, 0));
        return dotProduct / (magnitudeA * magnitudeB);
    }

    const docsToEmbeddings = weave.op(async function(docs: Article[]): Promise<Article[]> {
        const openai = new OpenAI();
        const enrichedDocs = await Promise.all(docs.map(async (doc) => {
            const response = await openai.embeddings.create({
                input: doc.text,
                model: "text-embedding-3-small"
            });
            return {
                ...doc,
                embedding: response.data[0].embedding
            };
        }));
        return enrichedDocs;
    });
    ```
  </Tab>
</Tabs>

<div id="create-a-rag-app">
  ## RAG アプリを作成する
</div>

次に、取得関数 `get_most_relevant_document` を `weave.op()` デコレータでラップし、`Model` クラスを作成します。`weave.init('<team-name>/rag-quickstart')` を呼び出して、後で確認できるように関数のすべての入力と出力のトラッキングを開始します。チーム名を指定しない場合、出力は [W\&B のデフォルト team または entity](/ja/platform/app/settings-page/user-settings#default-team) に記録されます。

<Tabs>
  <Tab title="Python">
    ```python lines theme={null}
    from openai import OpenAI
    import weave
    from weave import Model
    import numpy as np
    import asyncio

    @weave.op()
    def get_most_relevant_document(query):
        openai = OpenAI()
        query_embedding = (
            openai.embeddings.create(input=query, model="text-embedding-3-small")
            .data[0]
            .embedding
        )
        similarities = [
            np.dot(query_embedding, doc_emb)
            / (np.linalg.norm(query_embedding) * np.linalg.norm(doc_emb))
            for doc_emb in article_embeddings
        ]
        # 最も類似するドキュメントのインデックスを取得
        most_relevant_doc_index = np.argmax(similarities)
        return articles[most_relevant_doc_index]

    class RAGModel(Model):
        system_message: str
        model_name: str = "gpt-3.5-turbo-1106"

        @weave.op()
        def predict(self, question: str) -> dict: # 注: `question` は後で評価行からデータを選択するために使用されます
            from openai import OpenAI
            context = get_most_relevant_document(question)
            client = OpenAI()
            query = f"""Use the following information to answer the subsequent question. If the answer cannot be found, write "I don't know."
            Context:
            \"\"\"
            {context}
            \"\"\"
            Question: {question}"""
            response = client.chat.completions.create(
                model=self.model_name,
                messages=[
                    {"role": "system", "content": self.system_message},
                    {"role": "user", "content": query},
                ],
                temperature=0.0,
                response_format={"type": "text"},
            )
            answer = response.choices[0].message.content
            return {'answer': answer, 'context': context}

    # チーム名とプロジェクト名を設定
    weave.init('<team-name>/rag-quickstart')
    model = RAGModel(
        system_message="You are an expert in finance and answer questions related to finance, financial services, and financial markets. When responding based on provided information, be sure to cite the source."
    )
    model.predict("What significant result was reported about Zealand Pharma's obesity trial?")
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript lines theme={null}
    class RAGModel {
        private openai: OpenAI;
        private systemMessage: string;
        private modelName: string;
        private articleEmbeddings: Article[];

        constructor(config: {
            systemMessage: string;
            modelName?: string;
            articleEmbeddings: Article[];
        }) {
            this.openai = new OpenAI();
            this.systemMessage = config.systemMessage;
            this.modelName = config.modelName || "gpt-3.5-turbo-1106";
            this.articleEmbeddings = config.articleEmbeddings;
            this.predict = weave.op(this, this.predict);
        }

        async predict(question: string): Promise<{
            answer: string;
            context: string;
        }> {
            const context = await this.getMostRelevantDocument(question);
            
            const response = await this.openai.chat.completions.create({
                model: this.modelName,
                messages: [
                    { role: "system", content: this.systemMessage },
                    { role: "user", content: `Use the following information to answer the subsequent question. If the answer cannot be found, write "I don't know."
                        Context:
                        """
                        ${context}
                        """
                        Question: ${question}` }
                ],
                temperature: 0
            });

            return {
                answer: response.choices[0].message.content || "",
                context
            };
        }
    }
    ```
  </Tab>
</Tabs>

<div id="evaluating-with-an-llm-judge">
  ## LLM judge を用いた評価
</div>

アプリケーションを評価する簡単な方法がない場合、1 つの方法として、LLM を使ってそのいくつかの側面を評価できます。以下は、与えられた回答にたどり着くうえでコンテキストが有用だったかを検証するよう LLM judge に促し、コンテキストの適合率を測定しようとする例です。このプロンプトは、広く使われている [RAGAS フレームワーク](https://docs.ragas.io/en/stable/) をもとに拡張したものです。

<div id="defining-a-scoring-function">
  ### スコアリング関数を定義する
</div>

[Build an Evaluation pipeline tutorial](/ja/weave/tutorial-eval) と同様に、アプリをテストするためのサンプル行のセットとスコアリング関数を定義します。スコアリング関数は 1 行を受け取り、それを評価します。入力引数は行内の対応するキーと一致している必要があるため、ここでの `question` は行の辞書から取得されます。`output` はモデルの出力です。モデルへの入力も、その入力引数に基づいてサンプルから取得されるため、ここでの `question` も同様です。この例では `async` 関数を使用しているので、並列にすばやく実行できます。`async` の簡単な入門が必要な場合は、[こちら](https://docs.python.org/3/library/asyncio.html) を参照してください。

<Tabs>
  <Tab title="Python">
    ```python lines theme={null}
    from openai import OpenAI
    import weave
    import asyncio

    @weave.op()
    async def context_precision_score(question, output):
        context_precision_prompt = """Given question, answer and context verify if the context was useful in arriving at the given answer. Give verdict as "1" if useful and "0" if not with json output.
        Output in only valid JSON format.

        question: {question}
        context: {context}
        answer: {answer}
        verdict: """
        client = OpenAI()

        prompt = context_precision_prompt.format(
            question=question,
            context=output['context'],
            answer=output['answer'],
        )

        response = client.chat.completions.create(
            model="gpt-4-turbo-preview",
            messages=[{"role": "user", "content": prompt}],
            response_format={ "type": "json_object" }
        )
        response_message = response.choices[0].message
        response = json.loads(response_message.content)
        return {
            "verdict": int(response["verdict"]) == 1,
        }

    questions = [
        {"question": "What significant result was reported about Zealand Pharma's obesity trial?"},
        {"question": "How much did Berkshire Hathaway's cash levels increase in the fourth quarter?"},
        {"question": "What is the goal of Highmark Health's integration of Google Cloud and Epic Systems technology?"},
        {"question": "What were Rivian and Lucid's vehicle production forecasts for 2024?"},
        {"question": "Why was the Norwegian Dawn cruise ship denied access to Mauritius?"},
        {"question": "Which company achieved the first U.S. moon landing since 1972?"},
        {"question": "What issue did Intuitive Machines' lunar lander encounter upon landing on the moon?"}
    ]
    evaluation = weave.Evaluation(dataset=questions, scorers=[context_precision_score])
    asyncio.run(evaluation.evaluate(model)) # 注: 評価対象のモデルを定義する必要があります
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript lines theme={null}
    const contextPrecisionScore = weave.op(async function(args: {
        datasetRow: QuestionRow;
        modelOutput: { answer: string; context: string; }
    }): Promise<ScorerResult> {
        const openai = new OpenAI();
        
        const prompt = `Given question, answer and context verify if the context was useful...`;

        const response = await openai.chat.completions.create({
            model: "gpt-4-turbo-preview",
            messages: [{ role: "user", content: prompt }],
            response_format: { type: "json_object" }
        });

        const result = JSON.parse(response.choices[0].message.content || "{}");
        return {
            verdict: parseInt(result.verdict) === 1
        };
    });

    const evaluation = new weave.Evaluation({
        dataset: createQuestionDataset(),
        scorers: [contextPrecisionScore]
    });

    await evaluation.evaluate({
        model: weave.op((args: { datasetRow: QuestionRow }) => 
            model.predict(args.datasetRow.question)
        )
    });
    ```
  </Tab>
</Tabs>

<div id="optional-defining-a-scorer-class">
  ### 任意: `Scorer` クラスを定義する
</div>

アプリケーションによっては、カスタム評価クラスを作成したい場合があります。たとえば、特定のパラメーター (チャットモデルやプロンプトなど) 、各行に対する特定のスコアリング、集計スコアの特定の計算方法を備えた標準化済みの `LLMJudge` クラスを作成したいことがあります。Weave にはそのまま使用できる `Scorer` クラスがいくつか用意されており、カスタム `Scorer` も簡単に作成できます。以下の例では、カスタム `class CorrectnessLLMJudge(Scorer)` を作成する方法を示します。

大まかには、カスタム Scorer を作成する手順は非常にシンプルです。

1. `weave.flow.scorer.Scorer` を継承するカスタムクラスを定義します
2. `score` 関数をオーバーライドし、関数の各 call をトラッキングしたい場合は `@weave.op()` を追加します
   * この関数では、モデルの予測結果が渡される `output` 引数を定義する必要があります。モデルが "None" を返す可能性がある場合は、タイプ `Optional[dict]` として定義してください。
   * 残りの引数は、汎用的な `Any` または `dict` にすることも、`weave.Evaluate` クラスを使用してモデルを評価する際に使うデータセットから特定の列を選ぶこともできます。`preprocess_model_input` を使用する場合、これらの引数名は、その処理後の単一行の列名またはキーと完全に一致している必要があります。
3. *任意:* `summarize` 関数をオーバーライドして、集計スコアの計算をカスタマイズします。デフォルトでは、カスタム関数を定義しない場合、Weave は `weave.flow.scorer.auto_summarize` 関数を使用します。
   * この関数には `@weave.op()` デコレーターが必要です。

<Tabs>
  <Tab title="Python">
    ```python lines theme={null}
    from weave import Scorer

    class CorrectnessLLMJudge(Scorer):
        prompt: str
        model_name: str
        device: str

        @weave.op()
        async def score(self, output: Optional[dict], query: str, answer: str) -> Any:
            """pred、query、target を比較して予測の正確性をスコアリングします。
            Args:
                - output: 評価対象のモデルから提供される dict
                - query: 質問内容 - データセットで定義されたもの
                - answer: 正解の回答 - データセットで定義されたもの
            Returns:
                - 単一の dict {メトリクス名: 単一の評価値}"""

            # get_model は、指定したパラメーターに基づいて汎用的にモデルを取得する関数として定義されています (OpenAI,HF...)
            eval_model = get_model(
                model_name = self.model_name,
                prompt = self.prompt
                device = self.device,
            )
            # 評価を高速化するための非同期評価です - async である必要はありません
            grade = await eval_model.async_predict(
                {
                    "query": query,
                    "answer": answer,
                    "result": output.get("result"),
                }
            )
            # 出力の解析 - pydantic を使うとより堅牢に行えます
            evaluation = "incorrect" not in grade["text"].strip().lower()

            # Weave に表示される列名
            return {"correct": evaluation}

        @weave.op()
        def summarize(self, score_rows: list) -> Optional[dict]:
            """スコアリング関数によって各行に対して計算されたすべてのスコアを集計します。
            Args:
                - score_rows: dict のリスト。各 dict はメトリクスとスコアを持ちます
            Returns:
                - 入力と同じ構造を持つネストされた dict"""

            # 何も指定しない場合は、weave.flow.scorer.auto_summarize 関数が使用されます
            # return auto_summarize(score_rows)

            valid_data = [x.get("correct") for x in score_rows if x.get("correct") is not None]
            count_true = list(valid_data).count(True)
            int_data = [int(x) for x in valid_data]

            sample_mean = np.mean(int_data) if int_data else 0
            sample_variance = np.var(int_data) if int_data else 0
            sample_error = np.sqrt(sample_variance / len(int_data)) if int_data else 0

            # 追加の "correct" レイヤーは必須ではありませんが、UI 上で構造がわかりやすくなります
            return {
                "correct": {
                    "true_count": count_true,
                    "true_fraction": sample_mean,
                    "stderr": sample_error,
                }
            }
    ```
  </Tab>

  <Tab title="TypeScript">
    ```plaintext theme={null}
    この機能はまだ TypeScript では利用できません。
    ```
  </Tab>
</Tabs>

これを scorer として使用するには、初期化して `Evaluation` の `scorers` 引数に次のように渡します。

<Tabs>
  <Tab title="Python">
    ```python lines theme={null}
    evaluation = weave.Evaluation(dataset=questions, scorers=[CorrectnessLLMJudge()])
    ```
  </Tab>

  <Tab title="TypeScript">
    ```plaintext theme={null}
    この機能は TypeScript ではまだ利用できません。
    ```
  </Tab>
</Tabs>

<div id="pulling-it-all-together">
  ## ここまでをまとめる
</div>

RAG アプリで同じ結果を得るには、次のようにします。

* LLM 呼び出しと検索 step 関数を `weave.op()` でラップする
* (任意) `predict` 関数とアプリの詳細を持つ `Model` のサブクラスを作成する
* 評価用の例を収集する
* 1 つの例を採点するスコアリング関数を作成する
* `Evaluation` クラスを使用して、例に対して評価を実行する

**注:** `Evaluation` の非同期実行により、OpenAI や Anthropic などのモデルで rate limit に達することがあります。これを防ぐには、並列ワーカー数を制限する環境変数を設定します。たとえば `WEAVE_PARALLELISM=3` です。

以下にコード全体を示します。

<Tabs>
  <Tab title="Python">
    ```python lines {34,52-77} theme={null}
    from openai import OpenAI
    import weave
    from weave import Model
    import numpy as np
    import json
    import asyncio

    # 評価に使用するサンプル
    articles = [
        "Novo Nordisk and Eli Lilly rival soars 32 percent after promising weight loss drug results Shares of Denmarks Zealand Pharma shot 32 percent higher in morning trade, after results showed success in its liver disease treatment survodutide, which is also on trial as a drug to treat obesity. The trial “tells us that the 6mg dose is safe, which is the top dose used in the ongoing [Phase 3] obesity trial too,” one analyst said in a note. The results come amid feverish investor interest in drugs that can be used for weight loss.",
        "Berkshire shares jump after big profit gain as Buffetts conglomerate nears $1 trillion valuation Berkshire Hathaway shares rose on Monday after Warren Buffetts conglomerate posted strong earnings for the fourth quarter over the weekend. Berkshires Class A and B shares jumped more than 1.5%, each. Class A shares are higher by more than 17% this year, while Class B has gained more than 18%. Berkshire was last valued at $930.1 billion, up from $905.5 billion where it closed on Friday, according to FactSet. Berkshire on Saturday posted fourth-quarter operating earnings of $8.481 billion, about 28 percent higher than the $6.625 billion from the year-ago period, driven by big gains in its insurance business. Operating earnings refers to profits from businesses across insurance, railroads and utilities. Meanwhile, Berkshires cash levels also swelled to record levels. The conglomerate held $167.6 billion in cash in the fourth quarter, surpassing the $157.2 billion record the conglomerate held in the prior quarter.",
        "Highmark Health says its combining tech from Google and Epic to give doctors easier access to information Highmark Health announced it is integrating technology from Google Cloud and the health-care software company Epic Systems. The integration aims to make it easier for both payers and providers to access key information they need, even if it's stored across multiple points and formats, the company said. Highmark is the parent company of a health plan with 7 million members, a provider network of 14 hospitals and other entities",
        "Rivian and Lucid shares plunge after weak EV earnings reports Shares of electric vehicle makers Rivian and Lucid fell Thursday after the companies reported stagnant production in their fourth-quarter earnings after the bell Wednesday. Rivian shares sank about 25 percent, and Lucids stock dropped around 17 percent. Rivian forecast it will make 57,000 vehicles in 2024, slightly less than the 57,232 vehicles it produced in 2023. Lucid said it expects to make 9,000 vehicles in 2024, more than the 8,428 vehicles it made in 2023.",
        "Mauritius blocks Norwegian cruise ship over fears of a potential cholera outbreak Local authorities on Sunday denied permission for the Norwegian Dawn ship, which has 2,184 passengers and 1,026 crew on board, to access the Mauritius capital of Port Louis, citing “potential health risks.” The Mauritius Ports Authority said Sunday that samples were taken from at least 15 passengers on board the cruise ship. A spokesperson for the U.S.-headquartered Norwegian Cruise Line Holdings said Sunday that 'a small number of guests experienced mild symptoms of a stomach-related illness' during Norwegian Dawns South Africa voyage.",
        "Intuitive Machines lands on the moon in historic first for a U.S. company Intuitive Machines Nova-C cargo lander, named Odysseus after the mythological Greek hero, is the first U.S. spacecraft to soft land on the lunar surface since 1972. Intuitive Machines is the first company to pull off a moon landing — government agencies have carried out all previously successful missions. The company's stock surged in extended trading Thursday, after falling 11 percent in regular trading.",
        "Lunar landing photos: Intuitive Machines Odysseus sends back first images from the moon Intuitive Machines cargo moon lander Odysseus returned its first images from the surface. Company executives believe the lander caught its landing gear sideways on the surface of the moon while touching down and tipped over. Despite resting on its side, the company's historic IM-1 mission is still operating on the moon.",
    ]

    def docs_to_embeddings(docs: list) -> list:
        openai = OpenAI()
        document_embeddings = []
        for doc in docs:
            response = (
                openai.embeddings.create(input=doc, model="text-embedding-3-small")
                .data[0]
                .embedding
            )
            document_embeddings.append(response)
        return document_embeddings

    article_embeddings = docs_to_embeddings(articles) # 注意: 通常はこの処理を一度だけ実行し、埋め込みとメタデータをデータベースに保存します

    # 検索ステップにデコレーターを追加する
    @weave.op()
    def get_most_relevant_document(query):
        openai = OpenAI()
        query_embedding = (
            openai.embeddings.create(input=query, model="text-embedding-3-small")
            .data[0]
            .embedding
        )
        similarities = [
            np.dot(query_embedding, doc_emb)
            / (np.linalg.norm(query_embedding) * np.linalg.norm(doc_emb))
            for doc_emb in article_embeddings
        ]
        # 最も類似したドキュメントのインデックスを取得する
        most_relevant_doc_index = np.argmax(similarities)
        return articles[most_relevant_doc_index]

    # アプリの詳細を含む Model サブクラスを作成し、レスポンスを生成する predict 関数を定義する
    class RAGModel(Model):
        system_message: str
        model_name: str = "gpt-3.5-turbo-1106"

        @weave.op()
        def predict(self, question: str) -> dict: # 注意: `question` は後で評価行からデータを選択する際に使用されます
            from openai import OpenAI
            context = get_most_relevant_document(question)
            client = OpenAI()
            query = f"""以下の情報をもとに、後続の質問に答えてください。答えが見つからない場合は「わかりません」と書いてください。
            コンテキスト:
            \"\"\"
            {context}
            \"\"\"
            質問: {question}"""
            response = client.chat.completions.create(
                model=self.model_name,
                messages=[
                    {"role": "system", "content": self.system_message},
                    {"role": "user", "content": query},
                ],
                temperature=0.0,
                response_format={"type": "text"},
            )
            answer = response.choices[0].message.content
            return {'answer': answer, 'context': context}

    # チームとプロジェクトの名前を設定する
    weave.init('<team-name>/rag-quickstart')
    model = RAGModel(
        system_message="あなたは金融の専門家です。金融、金融サービス、金融市場に関する質問に答えてください。提供された情報に基づいて回答する際は、必ずソースを引用してください。"
    )

    # 質問と出力を使用してスコアを算出するスコアリング関数
    @weave.op()
    async def context_precision_score(question, output):
        context_precision_prompt = """質問、回答、コンテキストをもとに、そのコンテキストが回答を導き出すのに役立ったかどうかを検証してください。役立った場合は「1」、そうでない場合は「0」をJSON形式で出力してください。
        有効なJSON形式のみで出力してください。

        question: {question}
        context: {context}
        answer: {answer}
        verdict: """
        client = OpenAI()

        prompt = context_precision_prompt.format(
            question=question,
            context=output['context'],
            answer=output['answer'],
        )

        response = client.chat.completions.create(
            model="gpt-4-turbo-preview",
            messages=[{"role": "user", "content": prompt}],
            response_format={ "type": "json_object" }
        )
        response_message = response.choices[0].message
        response = json.loads(response_message.content)
        return {
            "verdict": int(response["verdict"]) == 1,
        }

    questions = [
        {"question": "Zealand Pharmaの肥満治療薬試験について報告された重要な結果は何ですか？"},
        {"question": "Berkshire Hathawayの第4四半期における現金保有額はどれくらい増加しましたか？"},
        {"question": "Highmark HealthによるGoogle CloudとEpic Systemsの技術統合の目的は何ですか？"},
        {"question": "RivianとLucidの2024年の車両生産予測はどのようなものでしたか？"},
        {"question": "Norwegian Dawnクルーズ船がモーリシャスへの入港を拒否された理由は何ですか？"},
        {"question": "1972年以来初めて米国の月面着陸を達成した企業はどこですか？"},
        {"question": "Intuitive Machinesの月面着陸船が着陸時に遭遇した問題は何ですか？"}
    ]

    # Evaluation オブジェクトを定義し、サンプルの質問とスコアリング関数を渡す
    evaluation = weave.Evaluation(dataset=questions, scorers=[context_precision_score])
    asyncio.run(evaluation.evaluate(model))
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript lines theme={null}
    require('dotenv').config();
    import { OpenAI } from 'openai';
    import * as weave from 'weave';

    interface Article {
        text: string;
        embedding?: number[];
    }

    const articles: Article[] = [
        { 
            text: `ノボ・ノルディスクとイーライ・リリーのライバル企業、有望な減量薬の結果を受けて株価32%急騰 デンマークのZealand Pharmaの株価は、肝疾患治療薬survodutideの試験で成功が示されたことを受け、午前の取引で32%上昇した。同薬は肥満治療薬としても臨床試験中である。「この試験により、6mgの用量が安全であることが確認された。これは現在進行中の[フェーズ3]肥満試験でも使用されている最高用量だ」とあるアナリストはレポートで述べた。この結果は、減量に使用できる薬への投資家の熱狂的な関心が高まる中で発表された。`
        },
        { 
            text: `バークシャーの株価、大幅増益を受けて急騰——バフェットのコングロマリットが時価総額1兆ドルに迫る ウォーレン・バフェット率いるBerkshire Hathawayが週末に第4四半期の好決算を発表したことを受け、同社株は月曜日に上昇した。クラスAおよびクラスB株はそれぞれ1.5%超上昇した。クラスA株は今年に入り17%超、クラスB株は18%超の上昇となっている。FactSetによると、バークシャーの時価総額は直近で9,301億ドルとなり、金曜日の終値時点の9,055億ドルから上昇した。バークシャーは土曜日、第4四半期の営業利益が84億8,100万ドルと発表した。これは前年同期の66億2,500万ドルから約28%増加しており、保険事業の大幅な利益増が牽引した。営業利益とは、保険、鉄道、公益事業などの各事業からの利益を指す。一方、バークシャーの現金保有額も過去最高水準に膨らんだ。同コングロマリットの第4四半期末の現金保有額は1,676億ドルとなり、前四半期に記録した1,572億ドルを上回った。`
        },
        { 
            text: `Highmark Health、GoogleとEpicの技術を統合して医師の情報アクセスを容易に Highmark Healthは、Google Cloudおよび医療ソフトウェア企業Epic Systemsの技術を統合すると発表した。同社によると、このインテグレーションは、情報が複数の場所や形式にまたがって保存されている場合でも、支払者とプロバイダーの双方が必要な重要情報にアクセスしやすくすることを目的としている。Highmarkは700万人の会員を持つ医療保険プランと、14の病院およびその他の関連組織からなるプロバイダーネットワークの親会社である。`
        }
    ];

    function cosineSimilarity(a: number[], b: number[]): number {
        const dotProduct = a.reduce((sum, val, i) => sum + val * b[i], 0);
        const magnitudeA = Math.sqrt(a.reduce((sum, val) => sum + val * val, 0));
        const magnitudeB = Math.sqrt(b.reduce((sum, val) => sum + val * val, 0));
        return dotProduct / (magnitudeA * magnitudeB);
    }

    const docsToEmbeddings = weave.op(async function(docs: Article[]): Promise<Article[]> {
        const openai = new OpenAI();
        const enrichedDocs = await Promise.all(docs.map(async (doc) => {
            const response = await openai.embeddings.create({
                input: doc.text,
                model: "text-embedding-3-small"
            });
            return {
                ...doc,
                embedding: response.data[0].embedding
            };
        }));
        return enrichedDocs;
    });

    class RAGModel {
        private openai: OpenAI;
        private systemMessage: string;
        private modelName: string;
        private articleEmbeddings: Article[];

        constructor(config: {
            systemMessage: string;
            modelName?: string;
            articleEmbeddings: Article[];
        }) {
            this.openai = new OpenAI();
            this.systemMessage = config.systemMessage;
            this.modelName = config.modelName || "gpt-3.5-turbo-1106";
            this.articleEmbeddings = config.articleEmbeddings;
            this.predict = weave.op(this, this.predict);
        }

        private async getMostRelevantDocument(query: string): Promise<string> {
            const queryEmbedding = await this.openai.embeddings.create({
                input: query,
                model: "text-embedding-3-small"
            });

            const similarities = this.articleEmbeddings.map(doc => {
                if (!doc.embedding) return 0;
                return cosineSimilarity(queryEmbedding.data[0].embedding, doc.embedding);
            });

            const mostRelevantIndex = similarities.indexOf(Math.max(...similarities));
            return this.articleEmbeddings[mostRelevantIndex].text;
        }

        async predict(question: string): Promise<{
            answer: string;
            context: string;
        }> {
            const context = await this.getMostRelevantDocument(question);
            
            const response = await this.openai.chat.completions.create({
                model: this.modelName,
                messages: [
                    { role: "system", content: this.systemMessage },
                    { 
                        role: "user", 
                        content: `以下の情報を参考に、後続の質問に回答してください。回答が見つからない場合は「わかりません」と記述してください。
                        コンテキスト:
                        """
                        ${context}
                        """
                        質問: ${question}`
                    }
                ],
                temperature: 0
            });

            return {
                answer: response.choices[0].message.content || "",
                context
            };
        }
    }

    interface ScorerResult {
        verdict: boolean;
    }

    interface QuestionRow {
        question: string;
    }

    function createQuestionDataset(): weave.Dataset<QuestionRow> {
        return new weave.Dataset<QuestionRow>({
            id: 'rag-questions',
            rows: [
                { question: "Zealand Pharmaの肥満試験について、どのような重要な結果が報告されましたか？" },
                { question: "Berkshire Hathawayの第4四半期における現金保有額はどれだけ増加しましたか？" },
                { question: "Highmark HealthによるGoogle CloudとEpic Systemsの技術統合の目的は何ですか？" }
            ]
        });
    }

    const contextPrecisionScore = weave.op(async function(args: {
        datasetRow: QuestionRow;
        modelOutput: { answer: string; context: string; }
    }): Promise<ScorerResult> {
        const openai = new OpenAI();
        
        const prompt = `質問、回答、およびコンテキストをもとに、そのコンテキストが回答の導出に役立ったかどうかを検証してください。役立った場合は "1"、そうでない場合は "0" をJSON形式で出力してください。
        有効なJSON形式のみで出力してください。

        question: ${args.datasetRow.question}
        context: ${args.modelOutput.context}
        answer: ${args.modelOutput.answer}
        verdict: `;

        const response = await openai.chat.completions.create({
            model: "gpt-4-turbo-preview",
            messages: [{ role: "user", content: prompt }],
            response_format: { type: "json_object" }
        });

        const result = JSON.parse(response.choices[0].message.content || "{}");
        return {
            verdict: parseInt(result.verdict) === 1
        };
    });

    async function main() {
        # チームとプロジェクト名を設定する
        await weave.init('<team-name>/rag-quickstart');
        
        const articleEmbeddings = await docsToEmbeddings(articles);
        
        const model = new RAGModel({
            systemMessage: "あなたは金融の専門家であり、金融、金融サービス、および金融市場に関する質問に回答します。提供された情報に基づいて回答する際は、必ずソースを引用してください。",
            articleEmbeddings
        });

        const evaluation = new weave.Evaluation({
            dataset: createQuestionDataset(),
            scorers: [contextPrecisionScore]
        });

        const results = await evaluation.evaluate({
            model: weave.op((args: { datasetRow: QuestionRow }) => 
                model.predict(args.datasetRow.question)
            )
        });
        
        console.log('評価結果:', results);
    }

    if (require.main === module) {
        main().catch(console.error);
    }
    ```
  </Tab>
</Tabs>

<div id="conclusion">
  ## まとめ
</div>

このチュートリアルでは、この例の取得 step のように、アプリケーションのさまざまな step にオブザーバビリティを組み込む方法を紹介しました。また、アプリケーションの応答を自動的に評価するために、LLM judge のような、より複雑なスコアリング関数を構築する方法も学びました。

<div id="next-steps">
  ## 次のステップ
</div>

エンジニア向けの実践的なRAG手法をさらに深く学ぶには、[RAG++ course](https://www.wandb.courses/courses/rag-in-production?utm_source=wandb_docs\&utm_medium=code\&utm_campaign=weave_docs)をご覧ください。Weights & Biases、Cohere、Weaviateが提供する本番運用対応のソリューションを通じて、パフォーマンスの最適化、コスト削減、アプリケーションの精度と関連性の向上に役立つ知見を学べます。
