1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

AIを使用して何かアプリの開発をしようと思ったのですが、ChatGPT SDKを利用するとクレジットが必要だったため、ローカルLLMで試せないかと思い実装してみました。

今回はOllamaを用いて、アプリ側からプロンプトを送りレスポンスを取得する方法を備忘録としてここに記載しています。

Ollamaのインストール

  1. 下記サイトからそれぞれの環境に合ったものをインストールする
    https://ollama.com/download
  2. アプリを実行しCLIへの権限を許可する
  3. ターミナルアプリで以下のコマンドを実行し、モデルを起動する
ollama run qwen3:8b

これによりqwen3:8bモデルが起動し、対話モードで動作します。
これでローカルLLMサーバーがリクエストを受け付けられる状態になります。

Requestクラス

public class OllamaRequest
{
    public string Model { get; set; } = "";
    public List<OllamaMessage> Messages { get; set; } = new();
    public bool Stream { get; set; }
}

Streamは/api/chatのレスポンス形式を切り替えるパラメータです。
trueにするとレスポンスが生成中のトークンごとに複数行のJSON(NDJSON)として返ってくるため、ReadFromJsonAsync<T>で単一オブジェクトとしてパースできなくなります。
今回は完成した回答を一度に受け取りたいため、bool型のデフォルト値であるfalseのまま使用しています。

Streamがfalseの場合のレスポンス

{
  "model": "qwen3:8b",
  "created_at": "2026-06-12T10:00:00Z",
  "message": {
    "role": "assistant",
    "content": "日本の首都は東京です。"
  },
  "done": true
}

Streamがtrueの場合のレスポンス

{"model":"qwen3:8b","created_at":"...","message":{"role":"assistant","content":"日本"},"done":false}
{"model":"qwen3:8b","created_at":"...","message":{"role":"assistant","content":"の"},"done":false}
{"model":"qwen3:8b","created_at":"...","message":{"role":"assistant","content":"首都"},"done":false}
{"model":"qwen3:8b","created_at":"...","message":{"role":"assistant","content":"は"},"done":false}
...
{"model":"qwen3:8b","created_at":"...","message":{"role":"assistant","content":""},"done":true,"total_duration":...}

Messageクラス

public class OllamaMessage
{
    public string Role { get; set; } = "";
    public string Content { get; set; } = "";
}

Responseクラス

using System.Text.Json.Serialization;

public class OllamaResponse
{
    [JsonPropertyName("model")]
    public string Model { get; set; }

    [JsonPropertyName("created_at")]
    public DateTime CreatedAt { get; set; }

    [JsonPropertyName("message")]
    public OllamaMessage Message { get; set; }

    [JsonPropertyName("done")]
    public bool Done { get; set; }

    [JsonPropertyName("done_reason")]
    public string DoneReason { get; set; }

    [JsonPropertyName("total_duration")]
    public long TotalDuration { get; set; }

    [JsonPropertyName("load_duration")]
    public long LoadDuration { get; set; }

    [JsonPropertyName("prompt_eval_count")]
    public int PromptEvalCount { get; set; }

    [JsonPropertyName("prompt_eval_duration")]
    public long PromptEvalDuration { get; set; }

    [JsonPropertyName("eval_count")]
    public int EvalCount { get; set; }

    [JsonPropertyName("eval_duration")]
    public long EvalDuration { get; set; }
}

ここで注意が必要なのは、Ollamaには/api/generateと/api/chatという2種類のエンドポイントが存在し、レスポンスの形式が異なる点です。

  • /api/generate: 生成結果がresponseフィールド(文字列)に入る
  • /api/chat: 生成結果がmessageオブジェクト(roleとcontent)に入る

今回は/api/chatを使用するため、ResponseプロパティではなくMessageプロパティ(OllamaMessage型)を持たせる必要があります。
ここを間違えると、リクエストは成功しているにもかかわらず本文が常に空になるという現象が起きるため、注意してください。

システムプロンプトで役割を与えてユーザー入力を処理するような用途では/api/chatの方が自然で扱いやすいです。

Service

using System.Net.Http.Json;

public class OllamaService
{
    private readonly HttpClient _httpClient;

    public OllamaService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<string> AskAsync(OllamaRequest request)
    {
        try 
        {
            var response = await _httpClient.PostAsJsonAsync(
                            "http://localhost:11434/api/chat",
                            request);
            response.EnsureSuccessStatusCode();

            var result =
                await response.Content.ReadFromJsonAsync<OllamaResponse>();

            Console.WriteLine($"Received response from Ollama: {result?.Message?.Content}");
            return result?.Message?.Content ?? "";
        }
        catch(Exception ex)
        {
            Console.WriteLine($"Failed to get advice from Ollama. Please check your Ollama server connection. Error: {ex.Message}");
            return string.Empty;
        }
    }
}

Program.cs

builder.Services.AddSingleton(new HttpClient());
builder.Services.AddSingleton<OllamaService>();

HttpClientの登録は、本来であればAddHttpClient<OllamaService>()(IHttpClientFactory経由)を使う方法が.NET/MAUIのベストプラクティスとして推奨されることが多いです。
今回はローカル検証用の最小構成として、シンプルにAddSingletonで済ませています。
本番運用やHttpClientの再利用・接続管理を意識する場合は、IHttpClientFactoryへの切り替えを検討してください。

builder.Services.AddHttpClient<OllamaService>();

プロンプト作成

// AIコメントを生成
var prompt = new OllamaRequest
{
    Model = "qwen3:8b",
    Messages = 
    [
        new OllamaMessage
        {
            Role = "system",
            Content = """
                ここでLLMに役割を与える。
                例:あなたは〇〇のアシスタントです。
                """
        },
        new OllamaMessage
        {
            Role = "user",
            Content = $"""
            ここにLLMに送りたい文章を記載する
            """
        }
    ]
};

string aiComment = await _ollamaService.AskAsync(prompt);

おわりに

今回はOllamaを使ってローカルLLMにMAUIアプリからリクエストを投げ、レスポンスを取得するまでの最小構成を実装しました。
クレジットを消費せずにAI機能を試作できるので、アイデア検証のフェーズでは特に有用だと感じました。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?