在前面幾章,我們使用的都是 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:本地大模型運行指南