无需 GPU 服务器,借助 OpenRouter 零成本搭建自己的大模型助手

服务器 0

一、搭建自己的大模型助手

大型模型的出现为许多领域带来了革命性的变化,从自然语言处理到计算机视觉,甚至是医学和金融领域。然而,对于许多开发者来说,使用一些开源的模型进行实验和应用却是一个挑战,因为它们通常需要昂贵的硬件资源来运行。大多数情况下,使用这些模型需要拥有一台配备高性能GPU的服务器,而这往往是一项昂贵的投资。而 OpenRouter 为使用者提供了部分开源模型的实现,可以通过API免费使用,主要聚焦在7B规模大小的模型,比如谷歌的 gemma-7bMistral AImistral-7b-instruct,一定程度避免了自己去部署大模型的成本。

本文就基于 OpenRouter 中免费模型接口的能力,使用谷歌的 gemma-7b 模型,搭建自己的大模型助手,实现效果如下:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

二、OpenRouter 使用

在实验前首先了解下 OpenRouter 是什么。OpenRouter 是一款整合了各类大模型的中间代理商,而且在国内无需梯子即可访问,通过 OpenRouter 可以调用超 100 种优秀的大模型,其中包括比较流行的 OpenAIChatGPT 系列(包括GPT4V),AnthropicClaude 系列,谷歌的 PaLMGemini 系列等,而且更换模型仅需修改模型的名字即可,无需修改调用代码得逻辑:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

官方地址如下:

https://openrouter.ai/

OpenRouter 没有对QQ邮箱做限制,支持 QQ 邮箱登录注册,一定程度上给国内的一些用户提供了便利,并且还免费提供了一批7B的模型,包括 nous-capybara-7b、mistral-7b-instruct、mythomist-7b、toppy-m-7b、cinematika-7b、gemma-7b-it

在这里插入图片描述

因此,当我们没有 GPU 服务器的时候,又想借助开源模型搭建一套自己的大模型助手时,就可以考虑使用 OpenRouter 了,注意使用前需要先注册账号,并生成 Api key

在这里插入图片描述

OpenRouter 主要以 http 的交互方式,因此几乎可以使用任何支持 http 的语言和框架去调用 ,同时也支持通过 OpenAIclient.chat.completions.create 方式调用:

例如:使用 Python 语言 http 的方式,调用 gemma-7b 模型:

import requestsimport jsonurl = "https://openrouter.ai/api/v1/chat/completions"model = "google/gemma-7b-it:free"request_headers = {    "Authorization": "Bearer 你的api_key",    "HTTP-Referer": "http://localhost:8088",    "X-Title": "test"}default_prompt = "You are an AI assistant that helps people find information."def llm(user_prompt,system_prompt=default_prompt):    messages = [        {"role": "system", "content": system_prompt},        {"role": "user", "content": user_prompt},    ]    request_json = {        "model": model,        "messages": messages,        "max_tokens": 2048    }    respose = requests.request(        url=url,        method="POST",        json=request_json,        headers=request_headers    )    return json.loads(respose.content.decode('utf-8'))['choices'][0]['message']['content']if __name__ == '__main__':    print(llm("你好,介绍一下你自己"))

运行输出:

在这里插入图片描述

使用 OpenAIclient.chat.completions.create 方式,调用 gemma-7b 模型:

from openai import OpenAImodel = "google/gemma-7b-it:free"default_prompt = "You are an AI assistant that helps people find information."client = OpenAI(    base_url="https://openrouter.ai/api/v1",    api_key="你的api_key",)def llm(user_prompt, system_prompt=default_prompt):    messages = [        {"role": "system", "content": system_prompt},        {"role": "user", "content": user_prompt},    ]    completion = client.chat.completions.create(        extra_headers={            "HTTP-Referer": "http://localhost:8088",            "X-Title": "test",        },        model=model,        messages=messages,        max_tokens = 2048    )    return completion.choices[0].message.contentif __name__ == '__main__':    print(llm("你好,介绍一下你自己"))

运行输出:

在这里插入图片描述

流式输出示例:

from openai import OpenAImodel = "google/gemma-7b-it:free"default_prompt = "You are an AI assistant that helps people find information."client = OpenAI(    base_url="https://openrouter.ai/api/v1",    api_key="你的api_key",)def llm(user_prompt, system_prompt=default_prompt):    messages = [        {"role": "system", "content": system_prompt},        {"role": "user", "content": user_prompt},    ]    completion = client.chat.completions.create(        extra_headers={            "HTTP-Referer": "http://localhost:8088",            "X-Title": "test",        },        model=model,        messages=messages,        max_tokens = 2048,        stream=True    )    for respose in completion:        if respose and respose.choices and len(respose.choices) > 0:            msg = respose.choices[0].delta.content            print(msg, end='', flush=True)if __name__ == '__main__':    llm("你好,介绍一下你自己")

运行输出:

在这里插入图片描述

三、搭建大模型助手

上面简单认识了 OpenRouter 的能力,下面基于 OpenRouter 上谷歌的 gemma-7b 模型搭建一个自己的大模型助手,简单的执行过程如下。

在这里插入图片描述

其中后端服务使用 Python + tornado 实现 Web 服务,前端使用基础的 Html + Jquery 的方式。

3.1 服务端搭建

所属依赖版本如下:

openai==0.27.8tornado==6.3.2

构建问答助手接口 server.py

接口我们接收两个参数 questionshistory ,其中 history 由后端维护并追加聊天记录,前端只负责临时存储,每次请求携带上一次请求返回的 history 即可,调用 OpenRouter 使用 OpenAI 库的方式。

整体实现逻辑如下:

from tornado.concurrent import run_on_executorfrom tornado.web import RequestHandlerimport tornado.genfrom openai import OpenAIimport jsonclass Assistant(RequestHandler):    model = "google/gemma-7b-it:free"    client = OpenAI(        base_url="https://openrouter.ai/api/v1",        api_key="你的api_key",    )    default_prompt = "You are an AI assistant that helps people find information."    def prepare(self):        self.executor = self.application.pool    def set_default_headers(self):        self.set_header('Access-Control-Allow-Origin', "*")        self.set_header('Access-Control-Allow-Headers', "Origin, X-Requested-With, Content-Type, Accept")        self.set_header('Access-Control-Allow-Methods', "GET, POST, PUT, DELETE, OPTIONS")    @tornado.gen.coroutine    def post(self):        json_data = json.loads(self.request.body)        if 'questions' not in json_data or 'history' not in json_data:            self.write({                "code": 400,                "message": "缺少必填参数"            })            return        questions = json_data['questions']        history = json_data['history']        result = yield self.do_handler(questions, history)        self.write(result)    @run_on_executor    def do_handler(self, questions, history):        try:            answer, history = self.llm(questions, history)            return {                "code": 200,                "message": "success",                "answer": answer,                "history": history            }        except Exception as e:            return {                "code": 400,                "message": str(e)            }    def llm(self, user_prompt, messages, system_prompt=default_prompt):        if not messages:            messages = []        messages.append({"role": "user", "content": user_prompt})        completion = self.client.chat.completions.create(            extra_headers={                "HTTP-Referer": "http://localhost:8088",                "X-Title": "test",            },            model=self.model,            messages=messages,            max_tokens=2048        )        answer = completion.choices[0].message.content        messages.append({"role": "assistant", "content": answer})        return answer, messages

路由配置,并启动服务 app.py

import tornado.webimport tornado.ioloopimport tornado.httpserverimport osfrom concurrent.futures.thread import ThreadPoolExecutorfrom server import Assistant## 配置class Config():    port = 8081    base_path = os.path.dirname(__file__)    settings = {        # "debug":True,        # "autore load":True,        "static_path": os.path.join(base_path, "resources/static"),        "template_path": os.path.join(base_path, "resources/templates"),        "autoescape": None    }# 路由class Application(tornado.web.Application):    def __init__(self):        handlers = [            ("/assistant", Assistant),            ("/(.*)$", tornado.web.StaticFileHandler, {                "path": os.path.join(Config.base_path, "resources/static"),                "default_filename": "index.html"            })        ]        super(Application, self).__init__(handlers, **Config.settings)        self.pool = ThreadPoolExecutor(10)if __name__ == '__main__':    app = Application()    httpserver = tornado.httpserver.HTTPServer(app)    httpserver.listen(Config.port)    print("start success", "prot = ", Config.port)    print("http://localhost:" + str(Config.port) + "/")    tornado.ioloop.IOLoop.current().start()

在这里插入图片描述

下面可以使用 Postman 进行测试:

请求内容:

{	"questions":"你好,介绍下你自己",	"history":[]}

输出示例:
在这里插入图片描述

从结果看接口访问正常,下面开始前端的搭建。

3.2 前端搭建

前端需要构建一个问答聊天界面,需要注意的是,模型返回的内容可能是 MD 格式,前端需要解析成html 格式展示,整体实现过程如下:

<!DOCTYPE html><html><head>    <meta charset="UTF-8">    <title>AI 聊天对话</title>    <style>        body {            font-family: Arial, sans-serif;            margin: 0;            padding: 0;        }        .container {            display: flex;            height: 100vh;        }        .left-panel {            flex: 15%;            background-color: #f2f2f2;            padding: 10px;        }        .right-panel {            flex: 85%;            background-color: #ffffff;            display: flex;            flex-direction: column;        }        .chat-log {            flex: 1;            overflow-y: auto;            padding: 20px;        }        .chat-bubble {            display: flex;            align-items: center;            margin-bottom: 10px;        }        .user-bubble {            justify-content: flex-end;        }        .bubble-content {            padding: 10px 15px;            border-radius: 20px;        }        .user-bubble .bubble-content {            background-color: #d6eaff;            color: #000000;        }        .ai-bubble .bubble-content {            background-color: #e5ece7;            color: #000;        }        .input-area {            display: flex;            align-items: center;            padding: 20px;        }        .input-text {            flex: 1;            padding: 10px;            margin-right: 10px;        }        .submit-button {            padding: 10px 20px;            background-color: #2196f3;            color: #ffffff;            border: none;            cursor: pointer;        }        li {            margin-top: 10px;        }        a {            text-decoration: none;        }        table {            border: 1px solid #000;            border-collapse: collapse;        }        table td, table th {            border: 1px solid #000;        }        table td, table th {            padding: 10px;        }        .language-sql {            width: 95%;            background-color: #F6F6F6;            padding: 10px;            font-weight: bold;            border-radius: 5px;            word-wrap: break-word;            white-space: pre-line;            /* overflow-wrap: break-word; */            display: block;        }        select {            width: 100%;            height: 30px;            border: 2px solid #6089a4;            font-size: 15px;            margin-top: 5px;        }        .recommendation{            color: #1c4cf3;            margin-top: 10px;        }    </style></head><body><div class="container">    <div class="left-panel">        <h2>智能问答助手</h2>        <h3>常用问题</h3>        <div class="recommendation">帮我写一个Java快速排序</div>        <div class="recommendation">Java 8有什么新特性</div>        <div class="recommendation">JVM优化建议</div>        <div class="recommendation">内存占用高,如何优化</div>        <div class="recommendation">MySQL优化建议</div>        <div class="recommendation">MySQL如何查看执行计划</div>    </div>    <div class="right-panel">        <div class="chat-log" id="chat-log">        </div>        <div class="input-area">            <input type="text" id="user-input" class="input-text" placeholder="请输入您的问题,回车或点击发送确定。">            <button id="submit" style="margin-left: 10px;width: 100px" onclick="sendMessage()" class="submit-button">                发送            </button>            <button style="margin-left: 20px;width: 100px;background-color: red" onclick="clearChat()"                    class="submit-button">清除记录            </button>        </div>    </div></div><script type="text/javascript" src="http://code.jquery.com/jquery-3.7.0.min.js"></script><script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script><script>    // 聊天历史记录    var messageHistory = [];    // 添加AI信息    function addAIMessage(message) {        $("#chat-log").append(            "<div class=/"chat-bubble ai-bubble/">/n" +            "    <div class=/"bubble-content/">" + message + "</div>/n" +            "</div>"        )    }    // 添加人类信息    function addUserMessage(message) {        $("#chat-log").append(            "<div class=/"chat-bubble user-bubble/">/n" +            "    <div class=/"bubble-content/">" + message + "</div>/n" +            "</div>"        )    }    // 滑动到底部    function slideBottom() {        let chatlog = document.getElementById("chat-log");        chatlog.scrollTop = chatlog.scrollHeight;    }    // 调用api    function chatApi(message) {        slideBottom();        data = {            questions: message,            history: messageHistory        };        $.ajax({            url: "http://127.0.0.1:8081/assistant",            type: "POST",            contentType: "application/json",            dataType: "json",            data: JSON.stringify(data),            success: function (res) {                if (res.code === 200) {                    let answer = res.answer;                    answer = marked.parse(answer);                    addAIMessage(answer);                    messageHistory = res.history;                } else {                    addAIMessage("服务接口调用错误。");                }            },            error: function (e) {                addAIMessage("服务接口调用异常。");            }        });    }    // 发送消息    function sendMessage() {        let userInput = $('#user-input');        let userMessage = userInput.val();        if (userMessage.trim() === '') {            return;        }        userInput.val("");        addUserMessage(userMessage);        chatApi(userMessage);    }    // 清空聊天记录    function clearChat() {        $("#chat-log").empty();        messageHistory = [];        addAIMessage("你好,请输入你想问的问题。");    }    // 初始化    function init() {        addAIMessage("你好,请输入你想问的问题。");        var submit = $("#submit");        var userInput = $("#user-input");        var focus = false;        // 监听输入框焦点        userInput.focus(function () {            focus = true;        }).blur(function () {            focus = false;        });        // 回车监听事件        document.addEventListener("keydown", function (event) {            if (event.keyCode === 13) {                console.log(focus);                if (focus) {                    submit.click();                }            }        });    }    init();</script></body></html>

运行效果:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

到此,我们自己的大模型助手就基本做好了!

也许您对下面的内容还感兴趣: