DelphiからOllamaを使う

Ollamaは，Hugging Faceなどで公開されている，大規模言語モデルをローカルのPCで動かすことができるツールです。MITライセンスで公開されています。

https://ollama.com/

https://github.com/ollama/ollama?tab=MIT-1-ov-file#readme

https://huggingface.co/

Delphi 12.2から追加されたスマート支援機能で，OllamaにCodellamaを導入して利用できるようになりました。
Ollamaの導入のしかたと，Delphiのスマート支援機能で利用するする方法については以下の記事でご確認いただければと思います。

- [Ollamaを使ってcodellamaを試す - Qiita](Qiitahttps://qiita.com/p_kato/items/1d99d41677392512f7ca)
- [Delphi 12.2のスマート支援機能を試す - Qiita](https://qiita.com/p_kato/items/63615480249e85dc8f29)

OllamaはREST APIでロードした大規模言語モデルを利用できるので，Delphiのアプリで利用しようと思います。

# OllamaにLlama3.2を導入
開発環境と同じPCにOllamaをインストールします。
OllamaをWindowsにインストールするには，[https://ollama.com/](https://ollama.com/)よりOllamaSetup.exeをダウンロードして実行します。
Ollamaのインストール後，以下をコマンドプロンプトより実行し，大規模言語モデルLlama3.2をインストールします。

```console:コマンドプロンプト
> ollama run llama3.2
```

# OllamaのAPIを調べる
OllamaのAPIのドキュメントから大規模言語モデルに質問するAPIを調べます。

https://github.com/ollama/ollama/blob/main/docs/api.md#request-no-streaming

これによると，ストリーミングしないリクエストは
```shell
curl http://localhost:11434/api/generate -d '{
  "model": "llama3.2",
  "prompt": "Why is the sky blue?",
  "stream": false
}'
```
で，結果は以下のように返ってくると書かれています。
ポート番号は11434で，modelにolammaへ導入した大規模言語モデル名を，promptに質問内容を書けば良いようです。
```json-doc
{
  "model": "llama3.2",
  "created_at": "2023-08-04T19:22:45.499127Z",
  "response": "The sky is blue because it is the color of the sky.",
  "done": true,
  "context": [1, 2, 3],
  "total_duration": 5043500667,
  "load_duration": 5025959,
  "prompt_eval_count": 26,
  "prompt_eval_duration": 325953000,
  "eval_count": 290,
  "eval_duration": 4709213000
}
```
responseに返答があるのでそれを取り出せればよいようです。

# RAD Studio の RESTデバッガ を利用する
Delphiを起動して **メインメニューーツールーRESTデバッガ** を選択します。
![Snap0000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33902/c47c7754-33cd-e0ef-a284-b10dd5e755a8.png)

### 要求の入力
![Snap0001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33902/6ef66340-b341-bc0e-ed60-21c191bd7567.png)
上記の要求ペインの要求タブで，上記の情報を以下のように入力します。

メソッド: POST
URL: http://localhost:11434/api/generate    
コンテンツタイプ: applicaiton/json
カスタム本体
```
{
  "model": "llama3.2",
  "prompt": "Why is the sky blue?",
  "stream": false
}
```
### 要求を送信
**「要求の送信」** ボタンをクリックすると，Ollamaに送信され，以下のように結果が返ってきます。

```json
{
    "model": "llama3.2",
    "created_at": "2024-12-01T07:16:24.9383655Z",
    "response": "The sky appears blue because of a phenomenon called Rayleigh scattering, named after the British physicist Lord Rayleigh, who first described it in the late 19th century.\n\nHere's what happens:\n\n1. **Sunlight enters Earth's atmosphere**: When sunlight enters our atmosphere, it consists of a spectrum of colors, including all the colors of the visible light spectrum (red, orange, yellow, green, blue, indigo, and violet).\n2. **Light scatters off tiny molecules**: The shorter wavelengths of light (like blue and violet) are more easily scattered by the tiny molecules of gases in the atmosphere, such as nitrogen and oxygen.\n3. **Blue light is scattered in all directions**: As a result of this scattering, the blue light is distributed throughout the atmosphere, reaching our eyes from all parts of the sky.\n4. **Other colors continue straight**: The longer wavelengths of light (like red and orange) are less affected by the scattering and continue to travel in a straight line, reaching our eyes from the direction of the sun.\n\nSince we're typically looking at the sky in the daytime when the sun is overhead, the blue light is scattered all around us, making the sky appear blue. The exact shade of blue can vary depending on factors like atmospheric conditions, pollution levels, and time of day.\n\nIn summary, the sky appears blue because of the scattering of shorter wavelengths (blue light) by tiny molecules in the atmosphere, while longer wavelengths (other colors) continue straight to our eyes from the direction of the sun.",
    "done": true,
    "done_reason": "stop",
    "context": [
        128006,
・・・中略・・・
        7160,
        13
    ],
    "total_duration": 2398489800,
    "load_duration": 29612900,
    "prompt_eval_count": 31,
    "prompt_eval_duration": 2000000,
    "eval_count": 310,
    "eval_duration": 2361000000
}
```

「コンポーネントのコピー」ボタンをクリックします。

なお，RESTデバッガツールの詳しい使い方については以下で確認できます。

https://docwiki.embarcadero.com/RADStudio/Athens/ja/%EF%BC%BBREST_%E3%83%87%E3%83%90%E3%83%83%E3%82%AC%EF%BC%BD%E3%81%AE%E5%88%A9%E7%94%A8

# DelphiでRESTクライアントを作成する
RESTデバッガ でコピーしたコンポーネントを，Delphiで利用しましょう。
Delphiを起動してVCLの新規プロジェクトを作成し，以下のようにフォームに Edit1(TEdit)，Memo1(TMemo)，Button1(TButton)を配置して，コピーしたコンポーネントをペーストします。
![Snap0016.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33902/5f2c1bc0-eb54-8507-83d4-9f8c3d3c53ab.png)

コードタブを表示して，JSONのシリアライズとデシリアライズをする関数を作成します。

```pascal:Unit1.pas
uses
  System.JSON.Types, System.JSON.Serializers; // JsonSerializer に必要
  
function OllamaGenerateRequestJSON(AModel,APrompt:string):string;
// generateでリクエストするJSONのテキストを出力する
type
  TOllamaGenerateRequest=record
    model:string;
    prompt:string;
    stream:boolean;
  end;
var
  OllamaGenerateRequest: TOllamaGenerateRequest;
begin
  OllamaGenerateRequest.model  := AModel;
  OllamaGenerateRequest.prompt := APrompt;
  OllamaGenerateRequest.stream := False;
  var JsonSerializer := TJsonSerializer.Create;
  try
    JsonSerializer.Formatting := TJsonFormatting.Indented;
    Result := 
      JsonSerializer.Serialize<TOllamaGenerateRequest>(OllamaGenerateRequest);
  finally
    JsonSerializer.Free;
  end;
end;

function OllamaGenerateResponseJSON(AContent:string):string;
// generateでリクエストするJSONのテキストを出力する
type
  TOllamaGenerateResponse=record
    response:string;
  end;
begin
  var JsonSerializer := TJsonSerializer.Create;
  var JSONResponse   := 
    JsonSerializer.Deserialize<TOllamaGenerateResponse>(AContent);
  Result := JSONResponse.response;
  JsonSerializer.Free;
end;
```
フォームデザイナに戻し，Button1をクリックして，OnClickイベントを以下のように記述します。
```pascal:Unit1.pas
procedure TForm1.Button1Click(Sender: TObject);
begin
  try
    RESTRequest1.Params[0].Value:=
      OllamaGenerateRequestJSON('llama3.2:latest',Edit1.Text);
    RESTRequest1.Execute;
    Memo1.Text:=OllamaGenerateResponseJSON(RESTResponse1.Content);
  finally
  end;
end;
```
完成したプログラムを実行し，Edit1に質問を入力してButton1をクリックすると，Ollamaを介してLlama3.2が返事をしてくれます。
![Snap0017.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33902/472be545-8641-8b8a-1572-e22f6fd10984.png)

# 補足
RESTデバッガからコピーされるコンポーネントの情報はそれほど多くないので，手順を自前で書いたり，スレッド化すれば，より使いやすくなると思います。

また，OllamaのAPIにはオプションを使って返事を同じにするためSeedを指定したり，コンテキストウインドウをどのくらい利用したかなどの情報も取得できます。履歴をもって会話するAPI(Chat)もありますので，これらを使えばもっと便利なRESTクライアントが作れるのではないかと思います。

# 謝辞
この記事を書くにあたって，@ht_deko
(Hideaki Tominaga)さんの記事や，@pik(Jun HOSOKAWA)さんの記事が大変参考になりました。感謝します。

https://qiita.com/ht_deko/items/eb3f987c50012109e781

https://qiita.com/pik/items/4e601c5db404d5a88dec

また，コードに問題があるようでしたらご指摘いただけるとありがたいです。