qinfengge

qinfengge

醉后不知天在水,满船清梦压星河
github
email
telegram

spring AI (五) 本地部署模型-llama3

在前面几章,我们使用的都是 ChatGPT 这种在线的大模型,即使各类大模型的 API 价格是越来越低的,但是在大量调用时依然是笔不小的支出,更不要说还有各种限制。而本地部署一些小模型在很多情况下也能提供一些不错的效果,不仅能够降低成本,限制还更少,最重要的是还能够对其进行一些自定义。这对公司及组织来说无疑是更有吸引力的。

本节将介绍使用 Ollama 来安装部署最新的 Llama3 模型,并使用 spring AI 进行调用。

安装 Ollama#

Ollama 的安装很简单,在官网 下载对应操作系统的安装包安装即可。安装并启动后,系统托盘会显示图标。

image

然后打开终端,输入 ollama -v 验证版本

image

此时 Ollama 已经安装好了,但它仅支持命令行的方式对话。如果想要在界面中使用可以安装一个 webUI(如果只是代码调用,此步可以省略)

windows 系统,如果已经安装了 WSLDocker 那可以使用以下命令运行

(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 官网进行搜索

image

原生的 llama3 主要是使用英文训练的,虽然可以用 prompt 强制指定它使用中文回答,但也经常被忽略。因此我更推荐使用一些用中文微调的 llama3 model

你可以搜索 llama3-chinese 来找到中文微调的版本

image

wangshenzhi/llama3-8b-chinese-chat-ollama-q8 这个我使用的模型举例

image

复制此处命令在终端运行即可

在大多数情况下仅推荐安装 8B 的模型,更大的模型最好安装在专用的计算卡上。

ollama run wangshenzhi/llama3-8b-chinese-chat-ollama-q8

ollama run 表示运行模型,如果模型未下载,则会先进行下载,如果模型已下载就会直接运行。

image

此时可以直接在终端进行对话

image

框架调用#

根据上一节的学习,我们可以直接复用代码,只需要修改模型的配置即可

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") 表示指定模型,注意一定要是全名称

其它的不需要更改,直接运行即可

image

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:本地大模型运行指南

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。