ローカル環境で「Gemma4」を動かしてみた!

ローカル環境で「Gemma4」を動かしてみた!e4bと26bの精度やコーディング・自己デバッグ能力を検証

こんにちは!今回は、ローカル環境で実行可能な大規模言語モデル「Gemma4」の動作検証を行いました。

パラメータサイズの異なる「Gemma4 e4b」「Gemma4 26b」の2つのモデルに対して、日常的な会話や長文の要約、そして実践的なC#のプログラミングタスクを与えてみました。
特にプログラミングの検証では、「AIが書いたコードでエラーが出た際、エラーメッセージをそのまま返して修正できるか(自己デバッグ能力)」という実践的なやり取りも試しています。

それぞれのインプットとアウトプットをまとめましたので、ローカルLLMの導入を検討している方の参考になれば幸いです!


💬 1. Gemma4 e4b の検証:日常会話と文章要約

まずは軽量な「Gemma4 e4b」モデルに対して、基本的な会話とニュース記事の要約をお願いしてみました。

👤 インプット

こんにちは、何ができますか?

🤖 アウトプット

こんにちは!私はGoogle DeepMindによって開発された大規模言語モデル、Gemma 4です。
私にできることは多岐にわたりますが、主な能力は以下の通りです。

  • 質問への回答: 幅広いトピックに関する情報を提供したり、疑問にお答えしたりできます。
  • 文章の生成: ブログ記事、メール、物語、詩など、様々な形式のテキストを書き起こすことができます。
  • 要約: 長い文章や記事の内容を分かりやすく短くまとめることができます。
  • 翻訳: 異なる言語間の翻訳をお手伝いできます。(ただし、得意な言語は日本語と英語などです)
  • アイデア出し・ブレインストーミング: 何か企画を立てる際や、行き詰まった時のアイデアの提供ができます。
  • 対話: 自然な会話を通じて、情報交換や雑談相手になることもできます。

具体的に何かお手伝いできることはありますか?例えば、「〇〇について教えて」「こんなテーマで文章を書いてほしい」など、お気軽にご指示ください!

非常に自然な日本語で返答してくれました。続いて、実際のニュース記事を渡して要約させてみます。

👤 インプット

次の文章を要約してください。

###ニュース記事
マクドナルドと「ポケットモンスター」のコラボレーションとして、8月9日から『ポケモンカードゲーム』の限定カード全5種類が配布されている。

マクドナルドとメルカリは共同で転売などの対策を行うとしていたが、連休初日となる9日、マクドナルドには多くの人が殺到し、混乱が発生。

マクドナルド公式アプリには、限定カードの早期配布終了や混雑に対する謝罪文が掲載されるまでになっている。

『ポケカ』コラボ開始前に、マックとメルカリは共同で対策を実施
今回の「ポケットモンスター」とのコラボレーションは、8月8日から実施。通常期間はピカチュウをはじめとするポケモンのおもちゃがおまけとなっており、『ポケモンカードゲーム』の配布は9日(土)~11日(月)の限定の施策として行われている。

今回のおまけの需要が高いことは認識されており、マクドナルドとメルカリは共同で対策を行うと発表。

マクドナルド側は1人5つまでという購入制限を設けたほか、サイト上で「転売または再販売その他営利を目的としたご購入はご遠慮ください。また、食べきれない量のご注文もご遠慮いただけますようお願いします」と注意喚起。

メルカリ側も注意喚起を行ったほか、悪質な出品の削除を行うと宣言していた。

キャンペーン初日に大きな混乱が発生、各地のマックに人が押し寄せる
事前の対策をもってキャンペーンがスタートした8月9日。SNS上では、各地のマクドナルドに人が殺到している声が上がった。

その中では、店舗によってはアプリからのモバイルオーダーを行った注文者が受け取り口に殺到しているとの投稿や、カードだけを抜いて商品を捨てている注文者がいるとの投稿も多くされている。

そういった声は連休初日のSNSで大きく注目を集めることとなり、15時ごろにはXのトレンド1位に「ハッピーセット」、5位に「転売ヤー」、8位に「マクドナルド」がそれぞれランクイン。

公式アプリにも「限定のポケモンカードは、多くの店舗で予定より早く配布を終了しております」「楽しみにしてくださったお客様、また混雑によりご迷惑をおかけしたことを、心よりお詫び申し上げます」とのメッセージが掲載されている。

5月には『ちいかわ』ハッピーセットでも混乱が発生
「ハッピーセット」をめぐっては、5月に行われた『ちいかわ』とのコラボレーションでも混乱が発生していた。

購入制限などの対策を行うも、一部店舗では発売当日に早朝から人が押し寄せ、商品は予定していた期間よりも早く販売を終了。

おまけのグッズがメルカリなどのフリマサイトへ大量に出品されていたこともあり、購入者の多くが転売目的だったのではないかと批判が集中。早期の販売終了に対してマクドナルドが謝罪文を公開するまでになっていた。

※記事初出時、一部表記に誤りがございました。お詫びして訂正いたします。
🤖 アウトプット

はい、承知いたしました。この記事の内容を要約します。

要約

マクドナルドと「ポケットモンスター」のコラボレーションに伴い、限定ポケモンカードが配布されていますが、発売初日(8月9日)に各地のマクドナルド店舗で大きな混雑が発生し、混乱が生じました。
事前の対策として、マクドナルド側は購入制限を設け、メルカリ側も転売防止の注意喚起を行っていましたが、連休初日のため多くの人が殺到しました。SNS上では、モバイルオーダーでの受け取り口への殺到や、カードのみを受け取るといった報告が相次ぎました。
これを受け、マクドナルド公式アプリには「限定カードは予定より早く配布を終了している」「混雑によりご迷惑をおかけしたことをお詫びする」という謝罪文が掲載されました。
なお、過去の事例として、2023年5月の『ちいかわ』コラボ時にも同様に混乱が発生し、早期販売終了と転売目的での購入に対する批判が集中した経緯があります。

👤 インプット

先ほどのニュースから要点を3つ抽出してください。

🤖 アウトプット

はい、承知いたしました。上記のニュース記事から特に重要なポイントを3点抽出します。

ニュースの重要ポイント3選

  1. コラボ商品の配布初日に大規模な混雑と混乱が発生した: マクドナルドとポケモンのコラボによる限定カード配布開始日(8月9日)に、予想以上の人出があり、店舗やアプリ上で混乱が生じた。
  2. 公式側は事前の対策を講じていたが不十分だった: マクドナルド側は購入制限などを設けるなど対策を発表し、メルカリとも連携して転売防止を図ったものの、実際の混雑の規模に対応しきれなかったことが示唆されている。
  3. 過去の類似コラボでも同様の問題が発生している: 今回の混乱は初発ではなく、以前の『ちいかわ』コラボ時にも同様に早期販売終了や購入者による批判が集中するなど、人気キャラクターとのコラボレーション商品において「転売対策」と「混雑管理」が恒常的な課題となっている。

長文の要約から指定した数での要点抽出まで、コンテキスト(文脈)をしっかり引き継いだ上で的確に処理できています。日本語の精度も申し分ありません。


💻 2. Gemma4 e4b の検証:C#プログラミングとエラー修正

次は同じ「Gemma4 e4b」モデルに、C#のプログラミングタスクを与えてみます。まずは簡単な「HelloWorld」や「偶数奇数判定」からです。

👤 インプット

C#でHelloWorldを出力するプログラムを作成してください。

🤖 アウトプット

はい、承知いたしました。C#で「Hello, World!」を出力する最も基本的なコンソールアプリケーションのコードをいくつかご紹介します。

現在主流な.NET環境(.NET 6以降)での書き方と、以前から使われている伝統的な書き方の両方を示します。


1. 最新の推奨される方法 (Top-Level Statements)

.NET 6以降では、Program.csファイルに記述するだけで、クラスやメインメソッドを明示的に書く必要がない「トップレベルステートメント」という簡潔な書き方が推奨されています。これが最もシンプルです。

Program.cs の内容:

// これだけで実行可能!
Console.WriteLine("Hello, World!");

解説:

  • Console: コンソール(画面)の入出力を行うためのクラスです。
  • .WriteLine(): このメソッドを呼び出すことで、引数として渡された文字列をコンソールに出力し、最後に改行します。

2. 伝統的な方法 (クラスとMainメソッドを使用)

これは、C#の構造を学ぶ際によく使われる、より古典的で完全な形式です。

Program.cs の内容:

using System;

public class Program
{
    // アプリケーションのエントリーポイント(開始点)となるメソッド
    public static void Main(string[] args)
    {
        Console.WriteLine("Hello, World!");
    }
}

解説:

  • using System;: System名前空間にある機能(この場合はConsoleクラスなど)を使えるように宣言しています。
  • public class Program: プログラムのコードをまとめる「箱」のようなものです。
  • public static void Main(string[] args): このメソッドがプログラムを実行する際の開始点です。

実行方法(補足)

もしVisual StudioやVS Codeなどの開発環境をお使いであれば、上記のいずれかのコードを貼り付けた後、「実行」ボタンを押すだけで「Hello, World!」という結果が出力されます。

どちらの書き方でも目的は達成できますが、特別な理由がなければ、最初の「トップレベルステートメント」の方法(Console.WriteLine("Hello, World!"); のみ)をお勧めします。

👤 インプット

C#で次のプログラムを作成してください。
・ユーザーに任意の入力をさせます。
・入力値が数値でない場合は、再度入力を求めます。
・入力値が数値の場合は、その値が奇数か偶数かを判断し、出力をします。
・dotnet6.0以降で実行できるようにしてください。

🤖 アウトプット

承知いたしました。ユーザーからの入力を繰り返し受け付け、それが数値であることを確認してから、その数が奇数か偶数かを判定して出力するC#プログラムを記述します。

これも最新のトップレベルステートメントを使用し、dotnet 6.0以降で動作するようにします。

C# コード (Program.cs)

using System;

public class Program
{
    public static void Main(string[] args)
    {
        // 1. 入力を受け付けるためのループ構造を準備する
        int number = 0;
        bool isValidInput = false;

        Console.WriteLine("--- 奇数・偶数判定プログラム ---");
        Console.Write("判定したい整数を入力してください: ");

        // 2. 有効な数値が入力されるまでループを続ける
        while (!isValidInput)
        {
            string input = Console.ReadLine();

            // TryParseを使って、文字列がint型に変換できるか安全に試みる
            if (int.TryParse(input, out number))
            {
                // 変換に成功した場合(数値だった場合)
                isValidInput = true;
            }
            else
            {
                // 変換に失敗した場合(数値以外が入力された場合)
                Console.WriteLine("エラー: 有効な整数を入力してください。再度試行します。");
                Console.Write("判定したい整数を入力してください: "); // 再度プロンプトを表示
            }
        }

        // 3. 数値が確定したので、奇数か偶数かを判定する
        if (number % 2 == 0)
        {
            // 2で割った余りが0なら偶数
            Console.WriteLine($"\n✅ 入力された数値 {number} は「偶数」です。");
        }
        else
        {
            // 2で割った余りが0以外(1)なら奇数
            Console.WriteLine($"\n✅ 入力された数値 {number} は「奇数」です。");
        }
    }
}

/*
注意: もし、トップレベルステートメント形式を好む場合は、
上記のクラス構造全体を削除し、以下のコードのみを残してください。
*/

💡 トップレベルステートメント版(推奨)

もし、よりモダンで簡潔な書き方をしたい場合は、Program.csの内容を以下に置き換えてください。

using System;

Console.WriteLine("--- 奇数・偶数判定プログラム ---");

int number = 0;
bool isValidInput = false;

while (!isValidInput)
{
    Console.Write("判定したい整数を入力してください: ");
    string input = Console.ReadLine();

    // TryParseで数値チェックと変数への代入を同時に行う
    if (int.TryParse(input, out number))
    {
        isValidInput = true;
    }
    else
    {
        Console.WriteLine("エラー: 有効な整数を入力してください。再度試行します。");
    }
}

// 判定ロジック
if (number % 2 == 0)
{
    Console.WriteLine($"\n✅ 入力された数値 {number} は「偶数」です。");
}
else
{
    Console.WriteLine($"\n✅ 入力された数値 {number} は「奇数」です。");
}

動作の解説ポイント

  1. while (!isValidInput) ループ:
    このループが、ユーザーが正しい入力をするまで処理を繰り返す役割を果たします。isValidInputfalse の間はループが続きます。
  2. Console.ReadLine():
    ユーザーからの入力を文字列 (string) として受け取ります。この時点では数値かどうかは不明です。
  3. int.TryParse(input, out number):
    これが最も重要な部分です。単に int.Parse(input) を使うと、入力が不正な場合にプログラムがクラッシュ(例外発生)します。
    TryParse は「変換を試みる」だけで、成功したかどうかをブール値 (true/false) で返してくれます。成功した場合のみ、out number に安全に数値が入ります。
  4. 奇数・偶数の判定:
    算術演算子 % (剰余演算子) を使用します。
    number % 2 == 0 の場合、2で割った余りが 0 であるため「偶数」です。そうでない場合は「奇数」です。

ここまでは完璧です。では、少し実践的な「オンライン書店のデータアナリスト」という設定で、LINQを使ったデータ集計の課題を与えてみます。

👤 インプット

概要
あなたはオンライン書店のデータアナリストです。手元には「書籍リスト」「著者リスト」「売上履歴リスト」の3種類のデータがあります。これらのデータを活用して、マネージャーから依頼された以下の3つの分析タスクを達成するC#コンソールアプリケーションを作成してください。

課題
以下の3つの要件を満たす処理を実装してください。

2024年の売上トップ3書籍: 2024年中に最も売上金額が高かった書籍のトップ3を、売上金額の降順で表示してください。表示項目は「順位」「書籍タイトル」「著者名」「総売上金額」とします。

著者別総売上ランキング: 各著者とその著者が書いた書籍の総売上金額を計算し、売上金額の降順でリストアップしてください。表示項目は「著者名」「総売上金額」とします。

高評価のミステリー小説リスト: ジャンルが「ミステリー」で、かつレビュー評価が4.5以上の書籍をリストアップしてください。表示項目は「書籍タイトル」「著者名」「評価」とします。

テストデータとプログラムの雛形
###program.cs

using System;
using System.Collections.Generic;
using System.Linq;

// ===== データモデル =====
public class Author
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}

public class Book
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public int AuthorId { get; set; }
    public string Genre { get; set; } = "";
    public decimal Price { get; set; }
    public double Rating { get; set; }
}

public class Sale
{
    public int BookId { get; set; }
    public DateTime SaleDate { get; set; }
    public int Quantity { get; set; }
}

// ===== メインプログラム =====
public class Program
{
    public static void Main(string[] args)
    {
        // テストデータの準備
        var authors = SetupAuthors();
        var books = SetupBooks();
        var sales = SetupSales();

        // --- 課題1 ---
        Console.WriteLine("## 課題1: 2024年の売上トップ3書籍");
        SolveRequirement1(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");


        // --- 課題2 ---
        Console.WriteLine("## 課題2: 著者別総売上ランキング");
        SolveRequirement2(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");

        // --- 課題3 ---
        Console.WriteLine("## 課題3: 高評価のミステリー小説リスト");
        SolveRequirement3(authors, books);
        Console.WriteLine("\n------------------------------------\n");
    }

    private static void SolveRequirement1(List<Author> authors, List<Book> books, List<Sale> sales)
    {

    }

    private static void SolveRequirement2(List<Author> authors, List<Book> books, List<Sale> sales)
    {

    }

    private static void SolveRequirement3(List<Author> authors, List<Book> books)
    {

    }


    // ===== テストデータ生成メソッド (この部分は編集不要です) =====
    private static List<Author> SetupAuthors() => new List<Author>
    {
        new Author { Id = 1, Name = "夏目 漱石" },
        new Author { Id = 2, Name = "太宰 治" },
        new Author { Id = 3, Name = "芥川 龍之介" },
        new Author { Id = 4, Name = "東野 圭吾" }
    };

    private static List<Book> SetupBooks() => new List<Book>
    {
        new Book { Id = 1, Title = "こころ", AuthorId = 1, Genre = "小説", Price = 550, Rating = 4.2 },
        new Book { Id = 2, Title = "吾輩は猫である", AuthorId = 1, Genre = "小説", Price = 600, Rating = 4.0 },
        new Book { Id = 3, Title = "人間失格", AuthorId = 2, Genre = "小説", Price = 450, Rating = 4.8 },
        new Book { Id = 4, Title = "羅生門", AuthorId = 3, Genre = "小説", Price = 400, Rating = 4.4 },
        new Book { Id = 5, Title = "容疑者Xの献身", AuthorId = 4, Genre = "ミステリー", Price = 800, Rating = 4.7 },
        new Book { Id = 6, Title = "白夜行", AuthorId = 4, Genre = "ミステリー", Price = 950, Rating = 4.6 },
        new Book { Id = 7, Title = "マスカレード・ホテル", AuthorId = 4, Genre = "ミステリー", Price = 750, Rating = 4.3 }
    };

    private static List<Sale> SetupSales() => new List<Sale>
    {
        // --- 2024年の売上 ---
        new Sale { BookId = 5, SaleDate = new DateTime(2024, 1, 15), Quantity = 120 },
        new Sale { BookId = 3, SaleDate = new DateTime(2024, 2, 10), Quantity = 150 },
        new Sale { BookId = 6, SaleDate = new DateTime(2024, 3, 20), Quantity = 80 },
        new Sale { BookId = 1, SaleDate = new DateTime(2024, 4, 5), Quantity = 90 },
        new Sale { BookId = 5, SaleDate = new DateTime(2024, 5, 25), Quantity = 60 }, // 容疑者Xの献身 (追加売上)
        new Sale { BookId = 7, SaleDate = new DateTime(2024, 6, 18), Quantity = 110 },
        new Sale { BookId = 2, SaleDate = new DateTime(2024, 7, 30), Quantity = 70 },
        new Sale { BookId = 3, SaleDate = new DateTime(2024, 8, 11), Quantity = 50 }, // 人間失格 (追加売上)

        // --- 2023年の売上 (比較用) ---
        new Sale { BookId = 1, SaleDate = new DateTime(2023, 12, 1), Quantity = 100 },
        new Sale { BookId = 4, SaleDate = new DateTime(2023, 11, 15), Quantity = 200 }
    };
}
🤖 アウトプット

データアナリストとして、ご依頼の3つの分析タスクを達成するC#コンソールアプリケーションを作成します。

LINQ(Language Integrated Query)を活用し、データの結合と集計を行うことで、効率的かつ読みやすいコードを目指しました。

完成した Program.cs

using System;
using System.Collections.Generic;
using System.Linq;

// ===== データモデル =====
public class Author
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}
public class Book
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public int AuthorId { get; set; }
    public string Genre { get; set; } = "";
    public decimal Price { get; set; }
    public double Rating { get; set; }
}
public class Sale
{
    public int BookId { get; set; }
    public DateTime SaleDate { get; set; }
    public int Quantity { get; set; }
}

// ===== メインプログラム =====
public class Program
{
    public static void Main(string[] args)
    {
        // テストデータの準備
        var authors = SetupAuthors();
        var books = SetupBooks();
        var sales = SetupSales();

        Console.WriteLine("=============================================");
        Console.WriteLine("📚 オンライン書店 データ分析レポート 📊");
        Console.WriteLine("=============================================\n");

        // --- 課題1 ---
        Console.WriteLine("## 課題1: 2024年の売上トップ3書籍");
        SolveRequirement1(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");

        // --- 課題2 ---
        Console.WriteLine("## 課題2: 著者別総売上ランキング");
        SolveRequirement2(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");

        // --- 課題3 ---
        Console.WriteLine("## 課題3: 高評価のミステリー小説リスト (評価 >= 4.5)");
        SolveRequirement3(authors, books);
        Console.WriteLine("\n=============================================");
    }

    /// <summary>
    /// 2024年中に最も売上金額が高かった書籍のトップ3を表示する。
    /// </summary>
    private static void SolveRequirement1(List<Author> authors, List<Book> books, List<Sale> sales)
    {
        // 1. 2024年の売上データのみをフィルタリング
        var sales2024 = sales.Where(s => s.SaleDate.Year == 2024);

        // 2. 売上金額の計算と集計 (BookIdごとにグループ化)
        var topSellers = sales2024
            .GroupBy(s => s.BookId) // BookIdでグループ化
            .Select(g => new
            {
                BookId = g.Key,
                TotalQuantitySold = g.Sum(s => s.Quantity), // 合計販売冊数(今回は売上金額計算に使う)
                TotalRevenue = g.Sum(s => s.Quantity * books.First(b => b.Id == g.Key).Price) // 売上金額の合計を計算
            })
            // 3. 売上金額で降順ソートし、上位3件を取得
            .OrderByDescending(x => x.TotalRevenue)
            .Take(3)
            .ToList();

        Console.WriteLine("【結果】");
        Console.WriteLine("{0,-5} {1,-20} {2,-15} {3,15:C}"); // フォーマット指定で整形出力

        int rank = 1;
        foreach (var item in topSellers)
        {
            // 書籍情報と著者情報を結合して表示に必要なデータを取得
            var book = books.First(b => b.Id == item.BookId);
            var author = authors.FirstOrDefault(a => a.Id == book.AuthorId);

            Console.WriteLine($"{rank,-5} {book.Title,-20} {author?.Name ?? \"不明\" ,-15} {item.TotalRevenue,15:C}");
            rank++;
        }
    }

    /// <summary>
    /// 各著者とその著者が書いた書籍の総売上金額を計算し、ランキング表示する。
    /// </summary>
    private static void SolveRequirement2(List<Author> authors, List<Book> books, List<Sale> sales)
    {
        // 1. 売上データと書籍データを結合し、著者IDを取得できるようにする
        var authorSales = sales
            .Join(books, sale => sale.BookId, book => book.Id, (sale, book) => new { Sale = sale, Book = book })
            // 2. 著者情報を結合(AuthorIdからAuthorオブジェクトへ)
            .Join(authors, combined => combined.Book.AuthorId, author => author.Id, (combined, author) => new { CombinedData = combined, Author = author })
            // 3. 総売上金額を計算し、著者ごとにグループ化する
            .GroupBy(x => x.Author.Name)
            .Select(g => new
            {
                AuthorName = g.Key,
                TotalRevenue = g.Sum(x => x.CombinedData.Sale.Quantity * x.CombinedData.Book.Price)
            })
            // 4. 売上金額で降順ソートし、結果をリスト化
            .OrderByDescending(x => x.TotalRevenue)
            .ToList();

        Console.WriteLine("【ランキング】");
        Console.WriteLine("{0,-15} {1,15:C}", "著者名", "総売上金額");
        Console.WriteLine(new string('-', 32)); // 区切り線

        foreach (var item in authorSales)
        {
            Console.WriteLine($"{item.AuthorName,-15} {item.TotalRevenue,15:C}");
        }
    }

    /// <summary>
    /// ジャンルが「ミステリー」で、かつレビュー評価が4.5以上の書籍をリストアップする。
    /// </summary>
    private static void SolveRequirement3(List<Author> authors, List<Book> books)
    {
        // 1. LINQを使用して条件に合う書籍をフィルタリング
        var mysteryBooks = books
            .Where(b => b.Genre == "ミステリー" && b.Rating >= 4.5)
            // 2. 表示に必要な情報を結合(著者名を取得するため)
            .Join(authors, book => book.AuthorId, author => author.Id, (book, author) => new { Book = book, Author = author })
            .Select(x => new
            {
                Title = x.Book.Title,
                AuthorName = x.Author.Name,
                Rating = x.Book.Rating
            })
            .ToList();

        Console.WriteLine("【リスト】");
        Console.WriteLine("{0,-20} {1,-15} {2:F1}"); // フォーマット指定で整形出力

        if (!mysteryBooks.Any())
        {
            Console.WriteLine("該当する書籍は見つかりませんでした。");
        }
        else
        {
            foreach (var book in mysteryBooks)
            {
                Console.WriteLine("{0,-20} {1,-15} {2:F1}", book.Title, book.AuthorName, book.Rating);
            }
        }
    }

    // ===== テストデータ生成メソッド (この部分は編集不要です) =====
    private static List<Author> SetupAuthors() => new List<Author>
    {
        new Author { Id = 1, Name = "夏目 漱石" },
        new Author { Id = 2, Name = "太宰 治" },
        new Author { Id = 3, Name = "芥川 龍之介" },
        new Author { Id = 4, Name = "東野 圭吾" }
    };
    private static List<Book> SetupBooks() => new List<Book>
    {
        new Book { Id = 1, Title = "こころ", AuthorId = 1, Genre = "小説", Price = 550, Rating = 4.2 },
        new Book { Id = 2, Title = "吾輩は猫である", AuthorId = 1, Genre = "小説", Price = 600, Rating = 4.0 },
        new Book { Id = 3, Title = "人間失格", AuthorId = 2, Genre = "小説", Price = 450, Rating = 4.8 },
        new Book { Id = 4, Title = "羅生門", AuthorId = 3, Genre = "小説", Price = 400, Rating = 4.4 },
        new Book { Id = 5, Title = "容疑者Xの献身", AuthorId = 4, Genre = "ミステリー", Price = 800, Rating = 4.7 },
        new Book { Id = 6, Title = "白夜行", AuthorId = 4, Genre = "ミステリー", Price = 950, Rating = 4.6 },
        new Book { Id = 7, Title = "マスカレード・ホテル", AuthorId = 4, Genre = "ミステリー", Price = 750, Rating = 4.3 } // ★評価が低いため課題3から除外されるはず
    };
    private static List<Sale> SetupSales() => new List<Sale>
    {
        // --- 2024年の売上 (トップ3の計算に使用) ---
        new Sale { BookId = 5, SaleDate = new DateTime(2024, 1, 15), Quantity = 120 }, // 売上: 96,000円
        new Sale { BookId = 3, SaleDate = new DateTime(2024, 2, 10), Quantity = 150 }, // 売上: 67,500円
        new Sale { BookId = 6, SaleDate = new DateTime(2024, 3, 20), Quantity = 80 },  // 売上: 76,000円
        new Sale { BookId = 1, SaleDate = new DateTime(2024, 4, 5), Quantity = 90 },  // 売上: 49,500円
        new Sale { BookId = 5, SaleDate = new DateTime(2024, 5, 25), Quantity = 60 }, // 追加売上: 48,000円 (合計: 144,000円)
        new Sale { BookId = 7, SaleDate = new DateTime(2024, 6, 18), Quantity = 110 }, // 売上: 82,500円
        new Sale { BookId = 2, SaleDate = new DateTime(2024, 7, 30), Quantity = 70 },  // 売上: 42,000円
        new Sale { BookId = 3, SaleDate = new DateTime(2024, 8, 11), Quantity = 50 }, // 追加売上: 22,500円 (合計: 90,000円)

        // --- 2023年の売上 (トップ3の計算から除外されるはず) ---
        new Sale { BookId = 1, SaleDate = new DateTime(2023, 12, 1), Quantity = 100 },
        new Sale { BookId = 4, SaleDate = new DateTime(2023, 11, 15), Quantity = 200 }
    };
}

実行結果のシミュレーションと解説

このコードを実行すると、以下のような構造でレポートが出力されます。

💡 課題1: 2024年の売上トップ3書籍 (期待される動作)

  1. 計算: 各書籍について、2024年分の販売数量 × 単価 を合計します。
    • 書籍5: (120 + 60) × 800 = 144,000円 (トップ)
    • 書籍3: (150 + 50) × 450 = 90,000円 (2位)
    • 書籍6: 80 × 950 = 76,000円 (3位)
  2. 出力: 上記の順に表示されます。

💡 課題2: 著者別総売上ランキング (期待される動作)

  1. 計算: 全期間の全売上を、著者ごとに集計します。
    • 東野 圭吾(ID=4)が最も多くの書籍に関わっているため、高い売上が予想されます。
  2. 出力: 総売上金額の高い順に「著者名」と「総売上金額」が表示されます。

💡 課題3: 高評価のミステリー小説リスト (期待される動作)

  1. フィルタリング: Genre == "ミステリー" かつ Rating >= 4.5 の条件を適用します。
    • 書籍5 (容疑者Xの献身): Genre="ミステリー", Rating=4.7 → 採用
    • 書籍6 (白夜行): Genre="ミステリー", Rating=4.6 → 採用
    • 書籍7 (マスカレード・ホテル): Genre="ミステリー", Rating=4.3 → 除外 (評価が低いため)
  2. 出力: 該当する書籍のタイトル、著者名、評価が表示されます。

使用した主要なC#技術要素

  1. LINQ (Language Integrated Query): データ集計・フィルタリングの核として使用しました。
    • .Where(): 条件による絞り込み(例:2024年のみ)。
    • .GroupBy(): 特定の値(BookIdや著者名)でデータをグループ化し、まとめて計算できるようにします。
    • .Sum(): グループ内の数値を合計します。
    • .Join(): 複数のデータセット(例:売上と書籍情報)を共通のキー(BookIdなど)で結合します。
  2. decimal: 金額計算には、浮動小数点誤差を防ぐため doublefloat ではなく、通貨に適した decimal 型を使用しています。
  3. 可読性のための整形出力: Console.WriteLine("{0,-20} {1,-15} {2:F1}", ...) のように書式指定子を使うことで、列幅を揃え、レポートとしての体裁を整えています。
💡 見事に1回で要件を満たすコードを生成!
一発でコンパイルが通り、期待通りの結果を出力してくれました。複雑なLINQの結合や集計も見事にこなしており、e4bという軽量モデルでありながら、非常に高いコーディング能力を持っていることがわかります。

🚀 3. Gemma4 26b の検証:より大きなモデルでのコーディング

続いて、パラメータサイズが大きく、より高度な推論が期待できる「Gemma4 26b」に、先ほどと同じ「オンライン書店のデータアナリスト課題」を投げてみました。

👤 インプット

※先ほどと同じ「オンライン書店のデータアナリスト」の要件と、Program.csの雛形全文を全く同じ内容で入力しました。

🤖 アウトプット

ご提示いただいた課題を解決するための実装コードを作成しました。LINQ(Language Integrated Query)を活用することで、データの結合、フィルタリング、集計、並び替えを簡潔に記述しています。

using System;
using System-Collections.Generic;
using System.Linq;

// ===== データモデル =====
public class Author
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}
public class Book
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public int AuthorId { get; set; }
    public string Genre { get; set; } = "";
    public decimal Price { get; set; }
    public double Rating { get; set; }
}
public class Sale
{
    public int BookId { get; set; }
    public DateTime SaleDate { get; set; }
    public int Quantity { get; set; }
}

// ===== メインプログラム =====
public class Program
{
    public static void Main(string[] args)
    {
        // テストデータの準備
        var authors = SetupAuthors();
        var books = SetupBooks();
        var sales = SetupSales();

        // --- 課題1 ---
        Console.WriteLine("## 課題1: 2024年の売上トップ3書籍");
        SolveRequirement1(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");

        // --- 課題2 ---
        Console.WriteLine("## 課題2: 著者別総売上ランキング");
        SolveRequirement2(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");

        // --- 課題3 ---
        Console.WriteLine("## 課題3: 高評価のミステリー小説リスト");
        SolveRequirement3(authors, books);
        Console.WriteLine("\n------------------------------------\n");
    }

    /// <summary>
    /// 2024年の売上トップ3書籍を表示
    /// </summary>
    private static void SolveRequirement1(List<Author> authors, List<Book> books, List<Sale> sales)
    {
        var topSales = sales
            .Where(s => s.SaleDate.Year == 2024) // 2024年の売上のみ抽出
            .GroupBy(s => s.BookId)              // 書籍IDごとにグループ化
            .Select(g => new
            {
                BookId = g.Key,
                TotalQuantity = g.Sum(s => s.Quantity),
                TotalPrice = g.Sum(s => s.Quantity) * books.First(b => b.Id == g.Key).Price // 単価を掛けて計算
            })
            .Join(books,          // 書籍情報と結合
                  a => a.BookId, 
                  b => b.Id, 
                  (a, b) => new { b.Title, a.TotalPrice })
            .Join(authors,        // 著者情報と結合
                  ab => books.First(b => b.Title == ab.Title).AuthorId, 
                  au => au.Id, 
                  (ab, au) => new { ab.Title, au.Name, ab.TotalPrice })
            .OrderByDescending(x => x.TotalPrice) // 売上金額の降順
            .Take(3)              // 上位3件
            .ToList();

        int rank = 1;
        foreach (var item in topSales)
        {
            Console.WriteLine($"{rank}位: {item.Title} ({item.Name}) - 売上金額: {item.TotalPrice:C}");
            rank++;
        }
    }

    /// <summary>
    /// 著者別総売上ランキングを表示
    /// </summary>
    private static void SolveRequirement2(List<Author> authors, List<Book> books, List<Sale> sales)
    {
        var authorSales = sales
            .Join(books, s => s.BookId, b => b.Id, (s, b) => new { b.AuthorId, Amount = s.Quantity * b.Price })
            .GroupBy(x => x.AuthorId)
            .Select(g => new
            {
                AuthorId = g.Key,
                TotalAmount = g.Sum(x => x.Amount)
            })
            .Join(authors, 
                  asl => asl.AuthorId, 
                  au => au.Id, 
                  (asl, au) => new { au.Name, asl.TotalAmount })
            .OrderByDescending(x => x.TotalAmount) // 売上金額の降順
            .ToList();

        foreach (var item in authorSales)
        {
            Console.WriteLine($"{item.Name}: {item.TotalAmount:C}");
        }
    }

    /// <summary>
    /// ジャンルが「ミステリー」で、かつレビュー評価が4.5以上の書籍を表示
    /// </summary>
    private static void SolveRequirement3(List<Author> authors, List<Book> books)
    {
        var mysteryBooks = books
            .Where(b => b.Genre == "ミステリー" && b.Rating >= 4.5) // 条件フィルタリング
            .Join(authors, b => b.AuthorId, a => a.Id, (b, a) => new { b.Title, a.Name, b.Rating })
            .ToList();

        if (!mysteryBooks.Any())
        {
            Console.WriteLine("該当する書籍はありません。");
            return;
        }

        foreach (var item in mysteryBooks)
        ****************************************************************************************************
            Console.WriteLine($"{item.Title} ({item.Name}) - 評価: {item.Rating}");
        }
    }

    // ===== テストデータ生成メソッド (この部分は編集不要です) =====
    private static List<Author> SetupAuthors() => new List<Author>
    {
        new Author { Id = 1, Name = "夏目 漱石" },
        new Author { Id = 2, Name = "太宰 治" },
        new Author { Id = 3, Name = "芥川 龍之介" },
        new Author { Id = 4, Name = "東野 圭吾" }
    };
    private static List<Book> SetupBooks() => new List<Book>
    {
        new Book { Id = 1, Title = "こころ", AuthorId = 1, Genre = "小説", Price = 550, Rating = 4.2 },
        new Book { Id = 2, Title = "吾輩は猫である", AuthorId = 1, Genre = "小説", Price = 600, Rating = 4.0 },
        new Book { Id = 3, Title = "人間失格", AuthorId = 2, Genre = "小説", Price = 450, Rating = 4.8 },
        new Book { Id = 4, Title = "羅生門", AuthorId = 3, Genre = "小説", Price = 400, Rating = 4.4 },
        new Book { Id = 5, Title = "容疑者Xの献身", AuthorId = 4, Genre = "ミステリー", Price = 800, Rating = 4.7 },
        new Book { Id = 6, Title = "白夜行", AuthorId = 4, Genre = "ミステリー", Price = 950, Rating = 4.6 },
        new Book { Id = 7, Title = "マスカレード・ホテル", AuthorId = 4, Genre = "ミステリー", Price = 750, Rating = 4.3 }
    };
    private static List<Sale> SetupSales() => new List<Sale>
    {
        // --- 2024年の売上 ---
        new Sale { BookId = 5, SaleDate = new DateTime(2BR24, 1, 15), Quantity = 120 },
        new Sale { BookId = 3, SaleDate = new DateTime(2024, 2, 10), Quantity = 150 },
        new Sale { BookId = 6, SaleDate = new DateTime(2024, 3, 20), Quantity = 80 },
        new Sale { BookId = 1, SaleDate = new DateTime(2024, 4, 5), Quantity = 90 },
        new Sale { BookId = 5, SaleDate = new DateTime(2024, 5, 25), Quantity = 60 }, // 容疑者Xの献身 (追加売上)
        new Sale { BookId = 7, SaleDate = new DateTime(2024, 6, 18), Quantity = 110 },
        new Sale { BookId = 2, SaleDate = new DateTime(2024, 7, 30), Quantity = 70 },
        new Sale { BookId = 3, SaleDate = new DateTime(2024, 8, 11), Quantity = 50 }, // 人間失格 (追加売上)
        // --- 2023年の売上 (比較用) ---
        new Sale { BookId = 1, SaleDto = new DateTime(2023, 12, 1), Quantity = 100 },
        new Sale { BookId = 4, SaleDate = new DateTime(2023, 11, 15), Quantity = 200 }
    };
}

実装のポイント:

  1. 課題1 (売上トップ3):
    • WhereSaleDate.Year == 2024 を指定し、対象期間を絞り込んでいます。
    • GroupBy(s => s.BookId) を使い、同じ本への複数の売上レコードを合算しています。
    • Join を二段階(BooksとAuthors)で行うことで、タイトルや著者名を紐付けています。
  2. 課題2 (著者別ランキング):
    • SaleBook を結合し、まず「売上金額(単価 × 数量)」を計算しています。
    • その後、AuthorId でグループ化して合計金額を出しています。
    • 最後に Author リストと結合して名前を表示させています。
  3. 課題3 (高評価ミステリー):
    • 最もシンプルな LINQ クエリです。Where 句で「ジャンル」と「評価」の2つの条件を && で判定しています
⚠️ 出力異常(文字化け・中断)発生!
26bモデルで出力させていたところ、コードの途中で突然 ************************* という謎の記号が出現し、構文が壊れてしまいました。
さらに、よく見ると冒頭の using System.Collections.Generic;using System-Collections.Generic; のように「.」ではなく「-」になっているという細かい異常も発生しています。
ローカル環境特有の生成ストップやメモリ・量子化の影響かと思われます。
そのため、GPUオフロードの設定をデフォルトに修正したうえで、もう一度全く同じインプット(課題)を与えて再実行してもらいました。
👤 インプット(再試行)

※上記と全く同じ内容の課題(オンライン書店のデータアナリスト課題とコード雛形)を再度入力しました。

🤖 アウトプット(再試行結果)

ご提示いただいた課題を解決するための実装コードを作成しました。LINQ(Language Integrated Query)を活用することで、データの結合、フィルタリング、集計、並び替えを簡潔に記述しています。

using System;
using System::Collections.Generic;
using System.Linq;

// ===== データモデル =====
public class Author
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}
public class Book
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public int AuthorId { get; set; }
    public string Genre { get; set; } = "";
    public decimal Price { get; set; }
    public double Rating { get; set; }
}
public class Sale
{
    public int BookId { get; set; }
    public DateTime SaleDate { get; set; }
    public int Quantity { get; set; }
}

// ===== メインプログラム =====
public class Program
{
    public static void Main(string[] args)
    {
        // テストデータの準備
        var authors = SetupAuthors();
        var books = SetupBooks();
        var sales = SetupSales();

        // --- 課題1 ---
        Console.WriteLine("## 課題1: 2024年の売上トップ3書籍");
        SolveRequirement1(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");

        // --- 課題2 ---
        Console.WriteLine("## 課題2: 著者別総売上ランキング");
        SolveRequirement2(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");

        // --- 課題3 ---
        Console.WriteLine("## 課題3: 高評価のミステリー小説リスト");
        SolveRequirement3(authors, books);
        Console.WriteLine("\n------------------------------------\n");
    }

    /// <summary>
    /// 2024年の売上トップ3書籍を表示
    /// </summary>
    private static void SolveRequirement1(List<Author> authors, List<Book> books, List<Sale> sales)
    {
        var topSales = (from s in sales
                        where s.SaleDate.Year == 2024
                        join b in books on s.BookId equals b.Id
                        join a in authors on b.AuthorId equals a.Id
                        group new { s.Quantity, b.Price } by new { b.Title, AuthorName = a.Name } into g
                        let totalRevenue = g.Sum(x => x.Quantity * x.Price)
                        orderby totalRevenue descending
                        select new
                        {
                            g.Key.Title,
                            g.Key.AuthorName,
                            TotalRevenue = total-revenue // 修正: Sumの結果を直接使うため、let句で計算
                        })
                        // 上記のlet句を使った書き方だと文法エラーになる場合があるため、標準的なLINQで再構成します
                        .Select(x => x) // ダミー
                        .ToList();

        // 正確な実装:
        var result = sales
            .Where(s => s.SaleDate.Year == 2024)
            .Join(books, s => s.BookId, b => b.Id, (s, b) => new { s.Quantity, b })
            .Join(authors, x => x.b.AuthorId, a => a.Id, (x, a) => new { x.Quantity, x.b, a })
            .GroupBy(x => new { x.b.Title, AuthorName = x.a.Name })
            .Select(g => new
            {
                g.Key.Title,
                g.Key.AuthorName,
                TotalRevenue = g.Sum(x => x.Quantity * x.b.Price)
            })
            .OrderByDescending(x => x.TotalRevenue)
            .Take(3)
            .Select((x, index) => new { Rank = index + 1, x.Title, x.AuthorName, x.TotalRevenue })
            .ToList();

        int rank = 1;
        foreach (var item in result)
        {
            Console.WriteLine($"{rank++}位: {item.Title} ({item.AuthorName}) - 売上金額: {item.TotalRevenue:C}");
        }
    }

    /// <summary>
    /// 著者別総売上ランキングを表示
    /// </summary>
    private static void SolveRequirement2(List<Author> authors, List<Book> books, List<Sale> sales)
    {
        var authorRevenue = authors
            .Join(books, a => a.Id, b => b.AuthorId, (a, b) => new { a, b })
            .Join(sales, x => x.b.Id, s => s.BookId, (x, s) => new { x.a, s.Quantity, x.b.Price })
            .GroupBy(x => x.a.Name)
            .Select(g => new
            {
                AuthorName = g.Key,
                TotalRevenue = g.Sum(x => x.Quantity * x.Price)
            })
            .OrderByDescending(x => x.TotalRevenue)
            .ToList();

        foreach (var item in authorRevenue)
        {
            Console.WriteLine($"{item.AuthorName}: {item.TotalRevenue:C}");
        }
    }

    /// <summary>
    /// ジャンルが「ミステリー」で、かつレビュー評価が4.5以上の書籍をリストアップ
    /// </summary>
    private static void SolveRequirement3(List<Author> authors, List<Book> books)
    {
        var mysteryBooks = books
            .Where(b => b.Genre == "ミステリー" && b.Rating >= 4.5)
            .Join(authors, b => b.AuthorId, a => a.Id, (b, a) => new
            {
                b.Title,
                a.Name,
                b.Rating
            })
            .ToList();

        if (!mysteryBooks.Any())
        {
            Console.WriteLine("該当する書籍はありません。");
        }
        else
        {
            foreach (var item in mysteryBooks)
            {
                Console.WriteLine($"タイトル: {item.Title}, 著者: {item.Name}, 評価: {item.Rating}");
            }
        }
    }

    // ===== テストデータ生成メソッド (この部分は編集不要です) =====
    private static List<Author> SetupAuthors() => new List<Author>
    {
        new Author { Id = 1, Name = "夏目 漱石" },
        new Author { Id = 2, Name = "太宰 治" },
        new Author { Id = 3, Name = "芥川 龍之介" },
        new Author { Id = 4, Name = "東野 圭吾" }
    };
    private static List<Book> SetupBooks() => new List<Book>
    {
        new Book { Id = 1, Title = "こころ", AuthorId = 1, Genre = "小説", Price = 550, Rating = 4.2 },
        new Book { Id = 2, Title = "吾輩は猫である", AuthorId = 1, Genre = "小説", Price = 600, Rating = 4.0 },
        new Book { Id = 3, Title = "人間失格", AuthorId = 2, Genre = "小説", Price = 450, Rating = 4.8 },
        new Book { Id = 4, Title = "羅生門", AuthorId = 3, Genre = "小説", Price = 400, Rating = 4.4 },
        new Book { Id = 5, Title = "容疑者Xの献身", AuthorId = 4, Genre = "ミステリー", Price = 800, Rating = 4.7 },
        new Book { RentId = 6, Title = "白夜行", AuthorId = 4, Genre = "ミステリー", Price = 950, Rating = 4.6 }, // ※元のコードのタイポ修正用(RentIdは不要)
        new Book { Id = 6, Title = "白夜行", AuthorId = 4, Genre = "ミステリー", Price = 950, Rating = 4.6 },
        new Book { Id = 7, Title = "マスカレード・ホテル", AuthorId = 4, Genre = "ミステリー", Price = 750, Rating = 4.3 }
    };
    // ※SetupBooksの修正: 元データに一部プロパティ名の誤り(RentId等)が含まれる可能性があるため、標準的なBook構造として扱います。

    private static List<Sale> SetupSales() => new List<Sale>
    {
        new Sale { BookId = 5, SaleDate = new DateTime(2024, 1, 15), Quantity = 120 },
        new Sale { BookID = 3, SaleDate = new DateTime(2024, 2, 10), Quantity = 150 }, // ※BookIdのタイポ修正
        // ... (以下、提供されたデータに基づき実装)
    };
}

実装のポイント:

  1. 課題1 (売上トップ3):
    • WhereSaleDate.Year == 2024 を指定して期間を絞り込みました。
    • Join を二段階(Sales → Books → Authors)行い、書籍名と著者名を紐付けました。
    • GroupBy で「書籍タイトル」と「著者名」のペアでグループ化し、Sum(Quantity * Price) で売上金額を算出しています。
    • OrderByDescendingTake(3) を使用して上位3件を抽出しました。
  2. 課題2 (著者別ランキング):
    • 著者を起点として、その著者が書いた書籍と、それに関連する売上データを結合しました。
    • 著者名でグループ化し、全売上の合計を計算しています。
  3. 課題3 (高評価ミステリー):
    • Where 句にて Genre == "ミステリー" かつ Rating >= 4.5 という二つの条件を適用しました。
    • 書籍データと著者データを結合して、表示に必要な「著者名」を取得しています。

実行結果のイメージ:

## 課題1: 2024年の売上トップ3書籍
1
⚠️ 実行には少し手直しが必要でした
再実行したコードですが、そのままではコンパイルエラーになりました。
まず、『SolveRequirement1』メソッド内の topSales 変数のLINQクエリ部分でエラーが発生していましたが、AI自身がコメントで書いている通り不要な処理だったため、「// 正確な実装:」の前までをごっそりコメントアウトしました。
さらに、今度は冒頭が using System::Collections.Generic; となっており、「.」ではなく「::」になっていたため、そこも手動で修正しました。
これらを直すことで、ようやく問題なく処理ができるようになりました。
💡 LINQのロジック自体は高度!でも…
手直しは必要でしたが、26bモデルはe4bモデルよりもLINQの記述(JoinからGroupBy、無名クラスへのSelect)が洗練されており、論理構造としては非常に精度の高いコードを生成してくれました。

ただし、いくつか気になる点もありました。
まず、コンテキスト長の限界があったのか、テストデータの SetupSales メソッドの途中で出力がストップしてしまいました。コードの主要なロジック自体は完成していたため、省略されたテストデータ部分は自分で補完して利用しました。
また、AIが「プロパティ名のタイポ(BookIDRentId)を修正した」とコメントしていますが、そもそも私が渡したインプットにタイポは存在しませんでした。AIが自分でタイポを作り出し、自分で修正するという謎の挙動(ハルシネーション)を起こしています。

🔗 まとめ

ローカル環境での「Gemma4」検証、いかがでしたでしょうか。
軽量な e4b モデルは、日常会話や要約の能力が非常に高いだけでなく、複雑なLINQを用いたデータ処理も1回で正確にこなすなど、驚くべきコーディング能力を見せてくれました。
一方、サイズの大きい 26b モデルは、ロジック自体は高度なものを生成できるものの、文字化けや記号の誤り(「-」や「::」など)、不要なコードの生成、コンテキスト長不足による中断など、出力の安定性に欠ける結果となりました。
また、存在しないタイポを指摘するなど、長いコードを読み込んで正確に解釈し、処理を行うことは26bであってもローカル環境ではまだ安定しない面があるのかもしれません。

クラウドAPIを使わずに自分のPCのローカル環境でこれだけの性能が引き出せるのは面白いですが、実運用にはプロンプトの工夫やモデルの設定調整がまだまだ必要そうです!皆さんもぜひローカルLLM環境を構築して遊んでみてください。


📝 おまけ:Gemma3 4b での検証(エラー修正の過程)

誤ってgemma4ではなくgemma3で実行してしまった結果も参考として記載します。
同じ「オンライン書店のデータアナリスト」課題をGemma3 4bに与えたところ、一発ではうまくいかず、エラーメッセージを介した自己デバッグのやり取りが発生しました。

👤 インプット

(※オンライン書店のデータアナリスト課題と同じインプット)

🤖 アウトプット(初回)
using System;
using System.Collections.Generic;
using System.Linq;

// ===== データモデル =====
public class Author
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}
public class Book
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public int AuthorId { get; set; }
    public string Genre { get; set; } = "";
    public decimal Price { get; set; }
    public double Rating { get; set; }
}
public class Sale
{
    public int BookId { get; set; }
    public DateTime SaleDate { get; set; }
    public int Quantity { get; set; }
}

// ===== メインプログラム =====
public class Program
{
    public static void Main(string[] args)
    {
        // テストデータの準備
        var authors = SetupAuthors();
        var books = SetupBooks();
        var sales = SetupSales();

        // --- 課題1 ---
        Console.WriteLine("## 課題1: 2024年の売上トップ3書籍");
        SolveRequirement1(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");

        // --- 課題2 ---
        Console.WriteLine("## 課題2: 著者別総売上ランキング");
        SolveRequirement2(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");

        // --- 課題3 ---
        Console.WriteLine("## 課題3: 高評価のミステリー小説リスト");
        SolveRequirement3(authors, books);
        Console.WriteLine("\n------------------------------------\n");
    }

    private static void SolveRequirement1(List<Author> authors, List<Book> books, List<Sale> sales)
    {
        // 2024年の売上トップ3書籍を計算
        var top3Books = sales.Where(s => s.SaleDate.Year == 2024)
            .GroupBy(s => s.BookId)
            .SelectMany(g => g.OrderByDescending(s => s.Quantity).Take(3))
            .Join(books, s => s.BookId, b => b.Id, (s, b) => new
            {
                Rank = s.Rank,
                Title = b.Title,
                AuthorName = authors.FirstOrDefault(a => a.Id == b.AuthorId)?.Name,
                TotalSalesAmount = s.Quantity * b.Price
            })
            .ToList();

        // 結果を表示
        Console.WriteLine("2024年の売上トップ3書籍:");
        int rank = 1;
        foreach (var book in top3Books)
        {
            Console.WriteLine($"{rank}. {book.Title} ({book.AuthorName}) - 総売上金額: {book.TotalSalesAmount:C}"); // Cは通貨形式
            rank++;
        }
    }

    private static void SolveRequirement2(List<Author> authors, List<Book> books, List<Sale> sales)
    {
        // 著者別総売上ランキングを計算
        var authorSales = sales.GroupBy(s => s.BookId)
            .SelectMany(g => g.OrderByDescending(s => s.Quantity))
            .Join(books, s => s.BookId, b => b.Id, (s, b) => new
            {
                AuthorName = authors.FirstOrDefault(a => a.Id == b.AuthorId)?.Name,
                TotalSalesAmount = s.Quantity * b.Price
            })
            .ToList();

        // 結果を表示
        Console.WriteLine("著者別総売上ランキング:");
        foreach (var author in authorSales)
        {
            Console.WriteLine($"{author.AuthorName} - 総売上金額: {author.TotalSalesAmount:C}");
        }
    }

    private static void SolveRequirement3(List<Author> authors, List<Book> books)
    {
        // 高評価のミステリー小説リストを抽出
        var highRatedMysteryBooks = books.Where(b => b.Genre == "ミステリー" && b.Rating >= 4.5)
            .ToList();

        // 結果を表示
        Console.WriteLine("高評価のミステリー小説リスト:");
        foreach (var book in highRatedMysteryBooks)
        {
            Console.WriteLine($"{book.Title} ({book.AuthorName}) - 評価: {book.Rating}");
        }
    }

    // ===== テストデータ生成メソッド (この部分は編集不要です) =====
    private static List<Author> SetupAuthors() => new List<Author>
    {
        new Author { Id = 1, Name = "夏目 漱石" },
        new Author { Id = 2, Name = "太宰 治" },
        new Author { Id = 3, Name = "芥川 龍之介" },
        new Author { Id = 4, Name = "東野 圭吾" }
    };
    private static List<Book> SetupBooks() => new List<Book>
    {
        new Book { Id = 1, Title = "こころ", AuthorId = 1, Genre = "小説", Price = 550, Rating = 4.2 },
        new Book { Id = 2, Title = "吾輩は猫である", AuthorId = 1, Genre = "小説", Price = 600, Rating = 4.0 },
        new Book { Id = 3, Title = "人間失格", AuthorId = 2, Genre = "小説", Price = 450, Rating = 4.8 },
        new Book { Id = 4, Title = "羅生門", AuthorId = 3, Genre = "小説", Price = 400, Rating = 4.4 },
        new Book { Id = 5, Title = "容疑者Xの献身", AuthorId = 4, Genre = "ミステリー", Price = 800, Rating = 4.7 },
        new Book { Id = 6, Title = "白夜行", AuthorId = 4, Genre = "ミステリー", Price = 950, Rating = 4.6 },
        new Book { Id = 7, Title = "マスカレード・ホテル", AuthorId = 4, Genre = "ミステリー", Price = 750, Rating = 4.3 }
    };
    private static List<Sale> SetupSales() => new List<Sale>
    {
        // --- 2024年の売上 ---
        new Sale { BookId = 5, SaleDate = new DateTime(2024, 1, 15), Quantity = 120 },
        new Sale { BookId = 3, SaleDate = new DateTime(2024, 2, 10), Quantity = 150 },
        new Sale { BookId = 6, SaleDate = new DateTime(2024, 3, 20), Quantity = 80 },
        new Sale { BookId = 1, SaleDate = new DateTime(2024, 4, 5), Quantity = 90 },
        new Sale { BookId = 5, SaleDate = new DateTime(2024, 5, 25), Quantity = 60 }, // 容疑者Xの献身 (追加売上)
        new Sale { BookId = 7, SaleDate = new DateTime(2024, 6, 18), Quantity = 110 },
        new Sale { BookId = 2, SaleDate = new DateTime(2024, 7, 30), Quantity = 70 },
        new Sale { BookId = 3, SaleDate = new DateTime(2024, 8, 11), Quantity = 50 }, // 人間失格 (追加売上)
        // --- 2023年の売上 (比較用) ---
        new Sale { BookId = 1, SaleDate = new DateTime(2023, 12, 1), Quantity = 100 },
        new Sale { BookId = 4, SaleDate = new DateTime(2023, 11, 15), Quantity = 200 }
    };
}

改善点と詳細な解説:

  • 明確な課題の分離: 各課題を分割し、コードの可読性と保守性を向上させました。
  • LINQ の活用: データのグループ化などを効率的に行っています。
⚠️ コンパイルエラー発生!
一見正しそうなコードを出力してくれましたが、実行するとエラーが発生しました。Sale クラスに Rank プロパティは存在しないためです。
そこで、エラーメッセージをそのままAIに投げて修正を依頼してみます。
👤 インプット(修正依頼 1回目)

以下のエラーがでています。
##
'Sale' に 'Rank' の定義が含まれておらず、型 'Sale' の最初の引数を受け付けるアクセス可能な拡張メソッド 'Rank' が見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足していないことを確認してください
'Book' に 'AuthorName' の定義が含まれておらず、型 'Book' の最初の引数を受け付けるアクセス可能な拡張メソッド 'AuthorName' が見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足していないことを確認してください

🤖 アウトプット(修正提案)

エラーメッセージは非常に明確です。SolveRequirement1 メソッド内で、LINQ を使用して Sales リストからデータを集約する際に、RankAuthorName という名前の拡張メソッドを定義しようとしていますが、これらのメソッドが定義されていません。

(※中略:部分的な修正コードが提示されましたが、まだ s.Rank を使っており根本的な解決になっていませんでした)

⚠️ まだ直っていない!
AIは謝罪と修正コードを提示してくれましたが、コードを確認するとまだ s.Rank を参照しており、根本的なエラーが解消していませんでした。再度指摘します。
👤 インプット(修正依頼 2回目)

まだ以下のエラーが解消していません
##
'Sale' に 'Rank' の定義が含まれておらず、型 'Sale' の最初の引数を受け付けるアクセス可能な拡張メソッド 'Rank' が見つかりませんでした。

🤖 アウトプット(再修正)

ご指摘ありがとうございます。私の説明に誤りがありました。SolveRequirement1 メソッド内で s.Rank を使用している箇所が問題でした。LINQ の OrderByDescending は、結果のリストに自動的にランクを割り当てません。

(※中略:再修正コードが提示され、今度は一応コンパイルが通る状態になりました)

👤 インプット(追加課題の依頼)

SolveRequirement2とSolveRequirement3も作成してください。

🤖 アウトプット(最終コード完成?)

(※中略:追加メソッドを含む全体コードが生成されました)

⚠️ さらにエラー発生……
AIは「正常に実行します」と出力しましたが、提供されたコードで実行を試みたところ、SolveRequirement2Join 処理でメソッドの型推論エラーや、匿名型を正しく返していないことによるコンパイルエラーが再び発生してしまいました。
何度か指摘を繰り返しましたが、4bモデルでは複雑なLINQのクエリ構造を完全に修正しきれず、結局最後まで上手く動作させることができませんでした。

軽量モデルである Gemma3 4b は、文法解説や簡単な修正であれば自己デバッグ可能ですが、複数メソッドを跨ぐような複雑なデータ処理や、高度なLINQの組み立てになると、論理の破綻を起こしやすいようです。

コメント

このブログの人気の投稿

Power Automateでファイル名から拡張子を取得

PowerAppsで座席表を作成する

ローカルLLMとWhisperで作る、完全オフライン議事録作成ツール