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