Ollama+FastAPI+React手把手構(gòu)建自己的本地大模型,支持WebSocket

上一篇我講了SSE(Server-Sent Events)的實(shí)現(xiàn)方式于购,這一篇講一下WebSocket的實(shí)現(xiàn)方式袍睡,首先來(lái)復(fù)習(xí)下使用的三種工具

  • Ollama:一個(gè)免費(fèi)的開(kāi)源框架,可以讓大模型很容易的運(yùn)行在本地電腦上
  • FastAPI:是一個(gè)用于構(gòu)建 API 的現(xiàn)代肋僧、快速(高性能)的 web 框架斑胜,使用
    Python 并基于標(biāo)準(zhǔn)的 Python 類(lèi)型提示
  • React:通過(guò)組件來(lái)構(gòu)建用戶界面的庫(kù)

簡(jiǎn)單來(lái)說(shuō)就類(lèi)似于LLM(數(shù)據(jù)庫(kù))+FastAPI(服務(wù)端)+React(前端)

image.png

1、下載Ollama之后使用Ollama完成大模型的本地下載和的運(yùn)行

ollama run llama3:8b

2嫌吠、模型運(yùn)行之后就可以調(diào)用了

curl http://localhost:11434/api/generate -d '{  
"model": "llama3:8b",  
"prompt": "Why is the sky blue?",  
"stream": false  
}'

3止潘、服務(wù)端用的基于Python的FastAPI

import uvicorn
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
import json
import requests
import asyncio

app = FastAPI(debug=True)

origins = [
    "http://localhost",  # 輸入自己前端項(xiàng)目的地址
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

llm_list = [{'label': 'qwen:latest', "value": 'qwen:latest'},
            {'label': 'llama3:8b', "value": 'llama3:8b'}, ]


@app.get("/llm/list")
def read_llm(model: str = 'qwen:latest'):
    return {"data": llm_list}


@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    try:
        while True:
            data = await websocket.receive_text()
            await asyncio.create_task(stream_response(websocket, data))
    except WebSocketDisconnect:
        await websocket.close()


async def stream_response(websocket: WebSocket, query: str):
    url = "http://localhost:11434/api/generate"  # 本地調(diào)用ollama的地址
    data = json.loads(query)
    print('data', data, type(data))
    payload = {"model": data['model_name'],
               "prompt": data['prompt'],
               "stream": True
               }
    print(payload)

    response = requests.post(url, json=payload)

    # 以512 字節(jié)的塊大小逐塊讀取響應(yīng)內(nèi)容,并用換行符分隔出每行內(nèi)容
    buffer = ""
    for chunk in response.iter_content(chunk_size=512):
        if chunk:
            buffer += chunk.decode('utf-8', errors='ignore')
            while '\n' in buffer:
                line, buffer = buffer.split('\n', 1)
                try:
                    data = json.loads(line)
                    await websocket.send_text(json.dumps(data['response']))
                except json.JSONDecodeError:
                    continue

    if buffer:
        try:
            data = json.loads(buffer)
            await websocket.send_text(json.dumps(data['response']))
        except json.JSONDecodeError:
            await websocket.send_text(buffer)


if __name__ == '__main__':
    uvicorn.run(app="app", host="127.0.0.1", port=8000, reload=True)

4辫诅、前端使用React

import { Input, Dropdown, Select, Form, Button, Spin, Space } from 'antd';
import { useEffect, useState } from 'react';
import { getList } from './service';
import { useRequest } from '@umijs/max';
import { useWebSocket } from 'ahooks';

const { TextArea } = Input;

export default () => {
  const { readyState, sendMessage, latestMessage, disconnect, connect } = useWebSocket(
    'ws://localhost:8000/ws',
    {
      onOpen: () => {
        console.log('connected');
      },
      onClose: () => {
        console.log('disconnected');
      },
      onMessage: (message) => {
        const { data } = message || {};
        console.log(data);
        if (data) {
          const result = JSON.parse(data);
          setValue((pre) => [...pre, result].join(''));
        }
      },
    },
  );

  const [form] = Form.useForm();
  const { data = [] } = useRequest(getList); // 用來(lái)獲取/llm/list接口覆山,返回可使用的大模型列表

  const [value, setValue] = useState('');

  const sharedProps = {
    style: { width: '100%' },
    autoSize: { minRows: 3, maxRows: 20 },
  };

  const onFinish = (values) => {
    sendMessage(JSON.stringify(values));
  };

  return (
    <div>
      <Form onFinish={onFinish} form={form}>
        <Form.Item name="model_name" label="模型">
          <Select style={{ width: 200 }} options={[...data]} />
        </Form.Item>
        <Form.Item name="prompt" label="提問(wèn)">
          <Input />
        </Form.Item>
        <Form.Item>
          <Space>
            <Button type="primary" htmlType="submit">
              提交
            </Button>
          </Space>
        </Form.Item>
      </Form>
      <TextArea value={value} {...sharedProps} />
    </div>
  );
};

??下面我們來(lái)分析一下SSE實(shí)現(xiàn)和WebSocket實(shí)現(xiàn)的優(yōu)劣以及為什么ChatGPT選擇SSE

Server-Sent Events (SSE)

優(yōu)點(diǎn)

  1. 簡(jiǎn)單實(shí)現(xiàn):SSE 實(shí)現(xiàn)相對(duì)簡(jiǎn)單,使用標(biāo)準(zhǔn)的 HTTP 協(xié)議泥栖,便于客戶端和服務(wù)器的實(shí)現(xiàn)和調(diào)試簇宽。
  2. 自動(dòng)重連:SSE 內(nèi)置自動(dòng)重連機(jī)制,當(dāng)連接斷開(kāi)時(shí)吧享,客戶端會(huì)自動(dòng)嘗試重新連接魏割。
  3. 瀏覽器原生支持:大多數(shù)現(xiàn)代瀏覽器原生支持 SSE,無(wú)需額外的庫(kù)或插件钢颂。
  4. 適合單向數(shù)據(jù)流:SSE 非常適合單向的數(shù)據(jù)流應(yīng)用場(chǎng)景钞它,例如實(shí)時(shí)通知、事件推送殊鞭、日志更新等遭垛。

缺點(diǎn)

  1. 單向通信:SSE 只能從服務(wù)器向客戶端推送數(shù)據(jù),客戶端不能主動(dòng)向服務(wù)器發(fā)送消息操灿。
  2. 僅支持文本數(shù)據(jù):SSE 僅支持文本數(shù)據(jù)的傳輸锯仪,不支持二進(jìn)制數(shù)據(jù)。
  3. 連接限制:部分瀏覽器對(duì)同一源的 SSE 連接數(shù)量有限制趾盐,這可能影響大規(guī)模應(yīng)用庶喜。

WebSocket

優(yōu)點(diǎn)

  1. 雙向通信:WebSocket 支持雙向通信小腊,客戶端和服務(wù)器都可以互相發(fā)送消息。
  2. 低延遲:WebSocket 保持一個(gè)持久連接久窟,可以提供低延遲的實(shí)時(shí)通信秩冈,適合高實(shí)時(shí)性要求的應(yīng)用。
  3. 支持二進(jìn)制數(shù)據(jù):WebSocket 支持發(fā)送二進(jìn)制數(shù)據(jù)和文本數(shù)據(jù)斥扛,滿足更多樣化的數(shù)據(jù)傳輸需求入问。

缺點(diǎn)

  1. 實(shí)現(xiàn)復(fù)雜:WebSocket 相對(duì)實(shí)現(xiàn)較為復(fù)雜,需要處理連接的建立稀颁、維護(hù)和關(guān)閉等問(wèn)題芬失。
  2. 瀏覽器兼容性:雖然大多數(shù)現(xiàn)代瀏覽器支持 WebSocket,但在某些環(huán)境下可能需要額外的庫(kù)或插件支持峻村。
  3. 防火墻和代理問(wèn)題:一些防火墻和代理服務(wù)器可能會(huì)阻止 WebSocket 連接,需要額外配置锡凝。

ChatGPT 選擇 SSE 的原因

  1. 單向數(shù)據(jù)流需求:ChatGPT 流式輸出的主要需求是服務(wù)器向客戶端推送數(shù)據(jù)(模型生成的文本)粘昨,這與 SSE 的單向數(shù)據(jù)流特性非常匹配。
  2. 簡(jiǎn)化實(shí)現(xiàn):SSE 使用 HTTP/2窜锯,可以更簡(jiǎn)化地集成到現(xiàn)有的 Web 服務(wù)器和應(yīng)用中张肾,而不需要處理 WebSocket 的復(fù)雜連接管理。
  3. 自動(dòng)重連機(jī)制:SSE 的自動(dòng)重連機(jī)制有助于提高連接的穩(wěn)定性和可靠性锚扎。
  4. 瀏覽器原生支持:SSE 受到現(xiàn)代瀏覽器的廣泛支持吞瞪,減少了客戶端的兼容性問(wèn)題。

總結(jié)

  • SSE 適用于單向推送數(shù)據(jù)的場(chǎng)景驾孔,實(shí)現(xiàn)簡(jiǎn)單芍秆,具有自動(dòng)重連機(jī)制,適合實(shí)時(shí)通知和事件推送翠勉。
  • WebSocket 適用于雙向?qū)崟r(shí)通信的復(fù)雜應(yīng)用妖啥,支持更高的實(shí)時(shí)性和多樣化的數(shù)據(jù)傳輸需求,但實(shí)現(xiàn)較為復(fù)雜对碌。

所以大家可以按照自己的業(yè)務(wù)需求來(lái)選擇適合自己的那一種方式??荆虱。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市朽们,隨后出現(xiàn)的幾起案子怀读,更是在濱河造成了極大的恐慌,老刑警劉巖骑脱,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件菜枷,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡叁丧,警方通過(guò)查閱死者的電腦和手機(jī)犁跪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門(mén)椿息,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人坷衍,你說(shuō)我怎么就攤上這事寝优。” “怎么了枫耳?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵乏矾,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我迁杨,道長(zhǎng)钻心,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任铅协,我火速辦了婚禮捷沸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘狐史。我一直安慰自己痒给,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布骏全。 她就那樣靜靜地躺著苍柏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪姜贡。 梳的紋絲不亂的頭發(fā)上试吁,一...
    開(kāi)封第一講書(shū)人閱讀 49,730評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音楼咳,去河邊找鬼熄捍。 笑死,一個(gè)胖子當(dāng)著我的面吹牛母怜,可吹牛的內(nèi)容都是我干的治唤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼糙申,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼宾添!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起柜裸,我...
    開(kāi)封第一講書(shū)人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤缕陕,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后疙挺,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體扛邑,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年铐然,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蔬崩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恶座。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖沥阳,靈堂內(nèi)的尸體忽然破棺而出跨琳,到底是詐尸還是另有隱情,我是刑警寧澤桐罕,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布脉让,位于F島的核電站,受9級(jí)特大地震影響功炮,放射性物質(zhì)發(fā)生泄漏溅潜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一薪伏、第九天 我趴在偏房一處隱蔽的房頂上張望滚澜。 院中可真熱鬧,春花似錦嫁怀、人聲如沸设捐。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)挡育。三九已至巴碗,卻和暖如春朴爬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背橡淆。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工召噩, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人逸爵。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓具滴,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親师倔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子构韵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容