前の章では、私たちが使用していたのは ChatGPT のようなオンラインの大規模モデルでした。さまざまな大規模モデルの API 価格はますます低下していますが、大量に呼び出す場合、依然としてかなりの支出となりますし、さまざまな制限もあります。一方で、いくつかの小規模モデルをローカルに展開することで、多くのケースで良好な結果を得ることができ、コストを削減できるだけでなく、制限も少なく、最も重要なのはカスタマイズが可能であることです。これは企業や組織にとって、間違いなくより魅力的です。
このセクションでは、Ollama を使用して最新の Llama3 モデルをインストールし、spring AI を使用して呼び出す方法を紹介します。
Ollama のインストール#
Ollama のインストールは非常に簡単で、公式サイトから対応するオペレーティングシステムのインストーラをダウンロードしてインストールするだけです。インストールして起動すると、システムトレイにアイコンが表示されます。
次に、ターミナルを開き、ollama -vと入力してバージョンを確認します。
これで Ollama はインストールされましたが、コマンドラインでの対話のみをサポートしています。インターフェースで使用したい場合は、webUI をインストールすることができます(コード呼び出しだけの場合はこのステップを省略できます)。
Windows システムでは、すでにWSLとDockerがインストールされている場合、以下のコマンドを使用して実行できます。
(1) CPU で実行する場合:
docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main
(2) GPU での実行をサポート:
docker run -d -p 3000:8080 --gpus=all -v ollama:/root/.ollama -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:ollama
インストールが完了したら、ローカルアドレス:http://127.0.0.1:3000 でアクセスします。
既知の問題
WSL にこのイメージをインストールした後、モデルを直接オンラインでダウンロードすることができず、ローカルにインストールされたモデルのみを読み取ることができますが、モデル選択後の対話は依然として失敗します。WSL と Windows ホストのポートマッピングの問題かどうかは不明です。
Windows ホストにDocker Desktopをインストールしてからこのイメージを実行すれば問題ありません。GPU で実行したい場合は、Conda 環境をインストールする必要があるかもしれません。
Llama3 モデル#
モデルをインストールするには、まずOllama 公式サイトで検索できます。
ネイティブの llama3 は主に英語でトレーニングされていますが、プロンプトを使用して中国語で回答するように強制することもできますが、しばしば無視されることがあります。したがって、私は中国語で微調整された llama3 モデルを使用することをお勧めします。
llama3-chineseを検索して、中国語で微調整されたバージョンを見つけることができます。
私が使用しているモデルの例として、wangshenzhi/llama3-8b-chinese-chat-ollama-q8を挙げます。
このコマンドをコピーしてターミナルで実行するだけです。
ほとんどの場合、8B モデルのインストールを推奨します。より大きなモデルは専用の計算カードにインストールするのが最適です。
ollama run wangshenzhi/llama3-8b-chinese-chat-ollama-q8
ollama runはモデルを実行することを示します。モデルがダウンロードされていない場合は、最初にダウンロードされ、すでにダウンロードされている場合は直接実行されます。
この時点で、ターミナルで直接対話できます。
フレームワーク呼び出し#
前のセクションの学習に基づいて、コードを再利用することができ、モデルの設定を変更するだけで済みます。
private static OllamaChatClient getClient(){
        var ollamaApi = new OllamaApi();
        return new OllamaChatClient(ollamaApi).withDefaultOptions(OllamaOptions.create()
                        .withModel("wangshenzhi/llama3-8b-chinese-chat-ollama-q8")
                        .withTemperature(0.4f));
    }
ここで、OllamaApiのデフォルトの baseUrl はhttp://localhost:11434です。モデルがローカルに展開されていない場合は、このアドレスを変更する必要があります。
.withModel("wangshenzhi/llama3-8b-chinese-chat-ollama-q8")はモデルを指定することを示します。必ず完全な名前であることに注意してください。
他の部分は変更する必要はなく、直接実行できます。
llama3 の既知の問題は次のとおりです:1. モデルが小さすぎて、返信が頻繁に誤っていたり幻覚を引き起こしたりすること、2. Function Calling をサポートしていないようです。
完全なコードは以下の通りです。
/**
 * @author lza
 * @date 2024/04/22-10:31
 **/
@RestController
@RequestMapping("ollama")
@RequiredArgsConstructor
@CrossOrigin
public class OllamaController {
    private static final Integer MAX_MESSAGE = 10;
    private static Map<String, List<Message>> chatMessage = new ConcurrentHashMap<>();
    /**
     * OpenAiChatClientを作成します
     * @return OpenAiChatClient
     */
    private static OllamaChatClient getClient(){
        var ollamaApi = new OllamaApi();
        return new OllamaChatClient(ollamaApi).withDefaultOptions(OllamaOptions.create()
                        .withModel("wangshenzhi/llama3-8b-chinese-chat-ollama-q8")
                        .withTemperature(0.4f));
    }
    /**
     * プロンプトを返します
     * @param message ユーザーが入力したメッセージ
     * @return プロンプト
     */
    private List<Message> getMessages(String id, String message) {
        String systemPrompt = "{prompt}";
        SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemPrompt);
        Message userMessage = new UserMessage(message);
        Message systemMessage = systemPromptTemplate.createMessage(MapUtil.of("prompt", "あなたは役に立つAIアシスタントです"));
        List<Message> messages = chatMessage.get(id);
        // メッセージが取得できなかった場合、新しいメッセージを作成し、システムプロンプトとユーザー入力のメッセージをメッセージリストに追加します
        if (messages == null){
            messages = new ArrayList<>();
            messages.add(systemMessage);
            messages.add(userMessage);
        } else {
            messages.add(userMessage);
        }
        return messages;
    }
    /**
     * 関数呼び出しを初期化します
     * @return ChatOptions
     */
    private ChatOptions initFunc(){
        return OpenAiChatOptions.builder().withFunctionCallbacks(List.of(
                FunctionCallbackWrapper.builder(new MockWeatherService()).withName("weather").withDescription("場所の天気を取得します").build(),
                FunctionCallbackWrapper.builder(new WbHotService()).withName("wbHot").withDescription("Weiboのホットリストを取得します").build(),
                FunctionCallbackWrapper.builder(new TodayNews()).withName("todayNews").withDescription("60秒で世界のニュースをチェックします").build(),
                FunctionCallbackWrapper.builder(new DailyEnglishFunc()).withName("dailyEnglish").withDescription("英語の毎日のインスピレーションの文").build())).build();
    }
    /**
     * 接続を作成します
     */
    @SneakyThrows
    @GetMapping("/init/{message}")
    public String init() {
        return String.valueOf(UUID.randomUUID());
    }
    @GetMapping("chat/{id}/{message}")
    public SseEmitter chat(@PathVariable String id, @PathVariable String message, HttpServletResponse response) {
        response.setHeader("Content-type", "text/html;charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
        OllamaChatClient client = getClient();
        SseEmitter emitter = SseEmitterUtils.connect(id);
        List<Message> messages = getMessages(id, message);
        System.err.println("chatMessageのサイズ: " + messages.size());
        System.err.println("chatMessage: " + chatMessage);
        if (messages.size() > MAX_MESSAGE){
            SseEmitterUtils.sendMessage(id, "対話回数が多すぎます。後で再試行してください🤔");
        }else {
            // モデルの出力ストリームを取得します
            Flux<ChatResponse> stream = client.stream(new Prompt(messages));
            // ストリーム内のメッセージをSSEで送信します
            Mono<String> result = stream
                    .flatMap(it -> {
                        StringBuilder sb = new StringBuilder();
                        String content = it.getResult().getOutput().getContent();
                        Optional.ofNullable(content).ifPresent(r -> {
                            SseEmitterUtils.sendMessage(id, content);
                            sb.append(content);
                        });
                        return Mono.just(sb.toString());
                    })
                    // メッセージを文字列に結合します
                    .reduce((a, b) -> a + b)
                    .defaultIfEmpty("");
            // メッセージをchatMessage内のAssistantMessageに保存します
            result.subscribe(finalContent -> messages.add(new AssistantMessage(finalContent)));
            // メッセージをchatMessageに保存します
            chatMessage.put(id, messages);
        }
        return emitter;
    }
無料で利用する#
以下は、いくつかの無料の llama3 モデルと API です。
Groq マスクスの会社で、専用のカードを使用していると聞いており、非常に高速です。
NVIDIA 老黄の会社で、たくさんの無料モデルが利用可能です。
cloudflare 大善人 CF の会社で、展開可能な多くのモデルがあり、無料の枠もあります。
Ollama
ローカルで Llama3 – 8B/70B 大モデルを展開する最も簡単な方法:CPU/GPU のサポート【3 つのオプション】
llama3Web インターフェースを簡単に構築 - Ollama + Open WebUI
ollama + AnythingLLM を使用してローカルで llama3 を迅速かつ簡単に展開する
Ollama:ローカル大モデルの実行ガイド
