在前面幾章,我們使用的都是 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 主要是使用英文訓練的,雖然可以用 prompt 強制指定它使用中文回答,但也經常被忽略。因此我更推薦使用一些用中文微調的 llama3 model
你可以搜索 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 Prompt
     */
    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", "you are a helpful AI assistant"));
        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("Get the weather in location").build(),
                FunctionCallbackWrapper.builder(new WbHotService()).withName("wbHot").withDescription("Get the hot list of Weibo").build(),
                FunctionCallbackWrapper.builder(new TodayNews()).withName("todayNews").withDescription("60s watch world news").build(),
                FunctionCallbackWrapper.builder(new DailyEnglishFunc()).withName("dailyEnglish").withDescription("A daily inspirational sentence in English").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:本地大模型運行指南
