AI之Ollama

介绍

什么是llama

LLaMA(Large Language Model Meta AI)是Meta开发的大规模预训练语言模型,基于Transformer架构,具有强大的自然语言处理能力。它在文本生成、问答系统、机器翻译等任务中表现出色。LLaMA模型有多个规模,从几亿到上千亿参数,适用于不同的应用场景。用户可以通过开源平台如Hugging Face获取LLaMA模型,并根据需要进行微调。LLaMA的灵活性和可扩展性使其在自然语言处理领域具有广泛的应用前景。

什么是 ollama

Ollama是一款用于本地安装和管理大规模预训练语言模型的工具。它简化了模型的下载、安装和使用流程,支持多种流行的模型如GPT-4和llama。Ollama通过易于使用的命令行界面和API,帮助用户快速部署和运行自然语言处理任务。它还支持多GPU配置和模型微调,适应各种计算资源和应用需求。总之,Ollama为研究人员和开发者提供了一个高效、灵活的本地化大模型解决方案。

下载

ollama 官网提供了各种平台的安装包,以下是下载地址:https://ollama.com/download

安装模型

ollama安装完成后就可以开始安装模型,先选择一个模型安装,ollama提供了一个页面供用户查询可以安装的开源模型。

https://ollama.com/search

在页面查询到想要安装的模块之后使用如下命令安装模型并启动:

ollama run [模型id]

安装示例

这里以llama3:latest为例:

ollama run llama3:latest

对话

安装完成后会自动跳转到对话,等待输入,可以输入帮助命令\?查看帮助;

添加UI

这里介绍如何给上面运行起来的大模型添加UI,我们选择open-webuiollama添加UI,ollama安装大模型后会启动一个服务端,这里使用open-webui调用服务;

什么是Open-WebUI

Open-WebUI 是一个开源的用户界面框架,旨在提供简便的工具和接口,帮助用户轻松地访问和管理各种深度学习模型,尤其是大规模预训练语言模型。以下是对Open-WebUI的简要介绍:

  • 开源框架: Open-WebUI是一个开源项目,提供了灵活且可定制的用户界面,用于与各种深度学习模型进行交互。

  • 模型管理: 通过Open-WebUI,用户可以方便地加载、配置和管理多个深度学习模型,包括 GPT-4BERT 等大规模预训练模型。

  • 用户友好: 它提供了直观的界面,简化了模型使用过程,使非技术用户也能轻松上手进行自然语言处理任务。

  • 集成支持: Open-WebUI 支持与多种后端深度学习框架(如 TensorFlowPyTorch)集成,提供高效的推理和训练功能。

  • 扩展性强: 用户可以根据需求自定义和扩展界面功能,以适应不同的应用场景和任务需求。
    总之,Open-WebUI为用户提供了一个高效、直观的界面,使得大规模深度学习模型的使用更加便捷和高效。

下载

以下是Open-WebUIGithub地址,从这里将项目下到本地
https://github.com/open-webui/open-webui

部署

这里使用的是Docker部署,只需要在项目文件下使用Docker命令启动一个容器即可,当ollamaOpen-WebUI部署在一台机器上,只需要运行:

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

如果ollama部署在服务器上,可以使用如下命令在启动Docker容器时指定ollama的地址

docker run -d -p 3000:8080 -e OLLAMA_BASE_URL=https://example.com -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main

OLLAMA_BASE_URL 是指 ollama 暴露的API地址,一般为服务器地址加 11434。如:OLLAMA_BASE_URL=<http://192.168.0.111:11434>

使用

部署完成后在浏览器打开http://localhost:3000,展示的是Open-WebUI的界面,第一次加载可能有点慢,然后注册一个用户就可以开始使用;

模型选择

在对话框的左上角有下拉框,可以看到已下载的所有模型,也可以搜索其他模型直接安装;

接入应用

本节介绍如何使用代码与AI进行交互,由于使用的是.net技术栈,这里使用MicrosoftSemanticKernel框架对接Ollama的聊天服务,SemanticKernel基于Open-Api协议,所以只要是遵循Open-Api的AI都可以接入,如:GPTCohere等;

LLM(大语言模型)的OpenAPI协议是一种用于描述和调用大语言模型服务的接口规范。OpenAPI(Open API Specification)是一个定义了如何描述、生产、消费和可视化RESTful风格的Web服务的规范。它提供了一种标准的、语言无关的方式来描述API,使得开发人员和机器能够理解API的功能和如何与之交互。
在LLM的上下文中,OpenAPI协议可以用来定义大语言模型的输入和输出格式、调用方式、参数设置等。通过遵循OpenAPI协议,开发人员可以更容易地将大语言模型集成到自己的应用程序中,而不需要了解模型的具体实现细节。
例如,一个遵循OpenAPI协议的LLM服务可能会定义以下接口:
输入:用户提供的文本、上下文信息等。
输出:模型生成的文本、预测结果等。
调用方式:通过HTTP POST请求发送输入数据,并接收返回的输出数据。
参数设置:如模型的超参数、生成文本的长度限制等。
通过这种方式,开发人员可以使用任何支持HTTP请求的编程语言来调用LLM服务,从而实现各种自然语言处理任务,如文本生成、文本分类、机器翻译等。
总之,LLM的OpenAPI协议为开发人员提供了一种标准的方式来与大语言模型进行交互,使得大语言模型的集成变得更加简单和高效。

Nuget

创建一个项目,这里使用的是WebApi项目,先安装SemanticKernel以及ollama connector

注意:ollama connector 还是 alpha 版本,请勿用于生产,搜索时勾选包括预发布版

配置服务

Program.cs文件中添加如下代码,添加一个Ollama客户端OllamaApiClient,并初始化Ollama服务地址和使用的模型Id,模型Id是下载时的模型名称,这里使用的是llama3:latest;
Program.cs

var endpoint = new Uri("http://localhost:11434");
var modelId = "llama3:latest";
builder.Services.AddSingleton(new OllamaApiClient(endpoint, modelId));

接入接口

注入客户端

创建一个AIChatController,将OllamaApiClient客户端注入到控制器中;

[Route("api/[controller]")]
[ApiController]
public class AIChatController : ControllerBase
{
    private readonly OllamaApiClient _ollamaApiClient;

    public AIChatController(OllamaApiClient ollamaApiClient)
    {
        _ollamaApiClient = ollamaApiClient;
    }
}

创建接口

创建一个接口用于接收客户端参数,这个接口需要有几点需求:

  1. 接口接收数据的同时接收用户id,以辨别是哪个用户发送的消息;
  2. 同时需要将客户的消息关联上下文,使对话可以关联起来,而不是简单、毫无联系的一问一答,所以需要记录历史消息;
  3. 由于AI的返回都是逐字逐句的流式返回,这里使用推送的形式响应客户端请求,所以使用SSE推送数据给客户端;

参数

先实现第一点需求,接收用户id和消息,这里简单使用一个model接收数据,正式做法是使用jwt解析token获取id;

/// <summary>
/// 聊天参数
/// </summary>
public class AIChatParam
{
    /// <summary>
    /// 用户id
    /// </summary>
    public int UserId { get; set; }

    /// <summary>
    /// 数据
    /// </summary>
    public string Data { get; set; }
}

历史记录

接下来实现第二点需求,将聊天的上下文做关联,这就需要将历史消息记录到某个媒介,然后打包发送到模型中,模型就可以根据上下文接着回答问题;
这里简单的创建了一个类,使用一个Dictionary在应用内存中维护这些历史消息,如果需要可以将消息记录放到数据库;

/// <summary>
/// 历史记录
/// </summary>
public static class AIChatHistory
{
    /// <summary>
    /// 数据
    /// </summary>
    private static Dictionary<int, List<Message>> _data = new Dictionary<int, List<Message>>();

    /// <summary>
    /// 获取历史记录
    /// </summary>
    /// <param name="userId">用户id</param>
    /// <returns></returns>
    public static List<Message>? GetHistory(int userId)
    {
        return _data.GetValueOrDefault(userId)??new List<Message>();

    }

    /// <summary>
    /// 最新记录
    /// </summary>
    /// <param name="userId">用户id</param>
    /// <returns></returns>
    public static Message GetLatestHistory(int userId)
    {
        Message msg = null;

        if (_data.TryGetValue(userId, out var userData))
        {
            msg = userData.Last();
        }

        return msg;
    }

    /// <summary>
    /// 添加
    /// </summary>
    /// <param name="userId">用户id</param>
    /// <param name="msg">消息</param>
    public static bool Add(int userId, Message msg)
    {

        if (_data.TryGetValue(userId, out var userData))
        {
            userData.Add(msg);
        }
        else
        {
            var list = new List<Message>();
            list.Add(new Message(ChatRole.System, "以下对话请使用中文"));
            list.Add(msg);
            _data.Add(userId, list);
        }

        return true;
    }

    /// <summary>
    /// 移除
    /// </summary>
    /// <param name="userId">用户id</param>
    /// <returns></returns>
    public static bool Clear(int userId)
    {
        if (_data.TryGetValue(userId, out _))
        {
            _data.Remove(userId);
        }

        return true;
    }
}

接入

做好了准备这里就可以接入了,这里创建一个接口,用于接收用户输入的参数,接口获取到模型的回答后将响应推送给客户端,步骤如下:

  1. 设置Content-Type;
  2. 载入历史记录;
  3. 调用ollama提供的接口;
  4. 获取回答后开始推送消息
  5. 将回答存到用户的历史记录中;
[Route("api/[controller]")]
[ApiController]
public class AIChatController : ControllerBase
{
    private readonly OllamaApiClient _ollamaApiClient;

    public AIChatController(OllamaApiClient ollamaApiClient)
    {
        _ollamaApiClient = ollamaApiClient;
    }

    /// <summary>
    /// Chat
    /// </summary>
    /// <param name="param">问答参数</param>
    /// <returns></returns>
    [HttpPost]
    public async Task Chat(AIChatParam param)
    {
        Response.ContentType = "text/event-stream";
        Response.Headers.Add("Cache-Control", "no-cache");
        Response.Headers.Add("Connection", "keep-alive");

        var userId = param.UserId;
        AIChatHistory.Add(userId, new Message { Role = ChatRole.User, Content = param.Data });
        var history = AIChatHistory.GetHistory(userId);

        var req = new OllamaSharp.Models.Chat.ChatRequest()
        {
            Messages = history,
            Stream = true
        };

        Console.OutputEncoding = Encoding.UTF8;
        Console.WriteLine("User:" + param.Data);

        var sb = new StringBuilder();
        var content = _ollamaApiClient.ChatAsync(req);
        Console.Write("Asist:");

        await foreach (var chatMessageContent in content)
        {
            var msg = chatMessageContent?.Message.Content;
            sb.Append(msg);
            Console.Write(msg);
            await Response.WriteAsync($"data: {msg}\n\n");
            await Response.Body.FlushAsync();
        }

        AIChatHistory.Add(userId, new Message { Role = ChatRole.Assistant, Content = sb.ToString() });
        Console.WriteLine();
    }
}

通过以上逻辑就可以实现获取模型的回答,同时推送到客户端;

清除上下文

有时,在对话的过程中我们可能需要重新问新的问题,不需要再关联之前的上下文了,这就需要将历史记录重新载入,由于这里使用的是Dictionary,我们只需要将用户的历史记录移除即可,如果历史记录存在数据库中就需要额外的逻辑控制历史记录;最终代码如下:

[Route("api/[controller]")]
[ApiController]
public class AIChatController : ControllerBase
{
    private readonly OllamaApiClient _ollamaApiClient;

    public AIChatController(OllamaApiClient ollamaApiClient)
    {
        _ollamaApiClient = ollamaApiClient;
    }

    /// <summary>
    /// Chat
    /// </summary>
    /// <param name="param">问答参数</param>
    /// <returns></returns>
    [HttpPost]
    public async Task Chat(AIChatParam param)
    {
        Response.ContentType = "text/event-stream";
        Response.Headers.Add("Cache-Control", "no-cache");
        Response.Headers.Add("Connection", "keep-alive");

        var userId = param.UserId;
        AIChatHistory.Add(userId, new Message { Role = ChatRole.User, Content = param.Data });
        var history = AIChatHistory.GetHistory(userId);

        var req = new OllamaSharp.Models.Chat.ChatRequest()
        {
            Messages = history,
            Stream = true
        };

        Console.OutputEncoding = Encoding.UTF8;
        Console.WriteLine("User:" + param.Data);

        var sb = new StringBuilder();
        var content = _ollamaApiClient.ChatAsync(req);
        Console.Write("Asist:");

        await foreach (var chatMessageContent in content)
        {
            var msg = chatMessageContent?.Message.Content;
            sb.Append(msg);
            Console.Write(msg);
            await Response.WriteAsync($"data: {msg}\n\n");
            await Response.Body.FlushAsync();
        }

        AIChatHistory.Add(userId, new Message { Role = ChatRole.Assistant, Content = sb.ToString() });
        Console.WriteLine();
    }

    /// <summary>
    /// 清除上下文
    /// </summary>
    /// <param name="userId">用户id</param>
    /// <returns></returns>
    [HttpGet]
    public async Task<IActionResult> Clear(int userId)
    {
        var res = AIChatHistory.Clear(userId);
        Console.WriteLine("清除上下文");

        return Ok(res);
    }
}
posted @   贰拾~  阅读(49)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示