Ollama+FastAPI+React手把手构建自己的本地大模型,支持SSE

最近大家都在玩LLM,我也凑了热闹,简单实现了一个本地LLM应用,分享给大家,百分百可以用哦~^ - ^

先介绍下我使用的三种工具:

  • Ollama:一个免费的开源框架,可以让大模型很容易的运行在本地电脑上
  • FastAPI:是一个用于构建 API 的现代、快速(高性能)的 web 框架,使用 Python 并基于标准的 Python 类型提示
  • React:通过组件来构建用户界面的库

简单来说就类似于LLM(数据库)+FastAPI(服务端)+React(前端)

开始搭建

1、下载Ollama之后使用Ollama完成大模型的本地下载和的运行

ollama run llama3:8b

这里我下载了最新的llama3:8b,电脑配置不高的话10b以内可以无痛运行,当然啦你也可以多下几个大模型,对比一下,我还下载了qwen,对比下来同一模型越大越聪慧,国内模型对中文支持度普遍好一点。

2、模型运行之后就可以调用了

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

3、新建一个python项目,实现代码如下:

import uvicorn
from fastapi import FastAPI, Request, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import json
import requests
from sse_starlette.sse import EventSourceResponse
import asyncio
import aiohttp

app = FastAPI(debug=True)

origins = [
    "http://localhost",
    # 输入自己前端项目的地址
]

# 设置跨域
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

urls = ["http://localhost:11434/api/generate"]


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}


# 这是一个异步生成器函数,它发送请求到 Ollama,并逐行读取响应内容,生成事件流。
async def stream_ollama_response(model_name, prompt):
    if model_name:
        url = urls[0]
        payload = {
            "model": model_name,
            "prompt": prompt,
            "stream": True
        }
        async with aiohttp.ClientSession() as session:
            async with session.post(url, json=payload) as response:
                async for line in response.content:
                    if line:
                        data = line.decode('utf-8').strip()
                        if data:
                            yield {"event": "message", "data": json.loads(data)["response"]}


# 开始对话,接收 model_name 和 prompt 参数。它调用 event_generator 函数,启动与 Ollama 的交互,并通过 EventSourceResponse 返回事件流
@app.get("/chat")
async def generate(request: Request, model_name: str = 'qwen:latest',
                   prompt: str = '请用中文介绍下中国古代四大名著之一的《红楼梦》'):
    async def event_generator():
        async for event in stream_ollama_response(model_name, prompt):
            yield event
            if await request.is_disconnected():
                break

    return EventSourceResponse(event_generator())


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

这是用SSE形式实现流式输出的demo,下一篇我再讲讲如何用WebSocket实现。

4、新建一个react项目,我用了antd大礼包+@microsoft/fetch-event-source这个微软的sse插件实现,代码如下:

import { Input, Dropdown, Select, Form, Button, Space } from 'antd';
import { useEffect, useState } from 'react';
import { getList, chat } from './service';
import { useRequest } from '@umijs/max';
import { fetchEventSource } from '@microsoft/fetch-event-source';

const { TextArea } = Input;

# 不能走代理哦,走了代理流式就失效了,?- ?
export const getHost = () => {
  const isDev = process.env.NODE_ENV === 'development';
  if (isDev) {
    return 'http://127.0.0.1:8000';
  } else {
    return '';
  }
};

export default () => {
  const [form] = Form.useForm();
  const { data = [] } = useRequest(getList);
  const [value, setValue] = useState('');
  const [start, setStart] = useState(null);
  const [end, setEnd] = useState(null);
  const [selected, setSelected] = useState(false);
  const [controller, setController] = useState(new AbortController());

  const sharedProps = {
    style: { width: '100%' },
    autoSize: { minRows: 3, maxRows: 20 },
    onChange: (e) => {
      setValue(e.target.value);
      setStart(e.target.selectionStart);
    },
    onClick: (e) => {
      setStart(e.target.selectionStart);
    },
    onSelect: (e) => {
      setStart(e.target.selectionStart);
      setEnd(e.target.selectionEnd);
      setSelected(e.target.value.substring(e.target.selectionStart, e.target.selectionEnd));
    },
  };

  const items = [
    {
      label: '重写这句话',
      key: '3',
    },
    {
      label: '把这句话翻译成中文',
      key: '4',
    },
  ];

  const menuClick = ({ key }) => {
    switch (key) {
      case '3':
        return reWrite();
      case '4':
        return reWrite('zh-CN');
    }
  };

  const reWrite = async (type) => {
    if (!selected) {
      return;
    }
    setValue(value.slice(0, start) + '重写中。。。' + value.slice(end));
    const res = await chat({
      model: form.getFieldValue('model'),
      prompt: type
        ? `${selected}”把“”中的这句话或单词翻译成中文,返回不要带格式,直接返回翻译结果`
        : selected,
    });
    setValue(value.slice(0, start) + res.data + value.slice(end));
  };

# 获取数据流
  const fetchData = async (url) => {
    await fetchEventSource(url, {
      method: 'GET',
      signal: controller.signal,
      onopen(res) {
        if (res.ok && res.status === 200) {
          console.log('Connection made ', res);
        } else if (res.status >= 400 && res.status < 500 && res.status !== 429) {
          errorHandler(res);
          console.log('Client side error ', res);
        }
      },
      onmessage(event) {
        console.log(event);
        setValue((data) => [...data, event.data].join(''));
      },
      onclose() {
        console.log('Connection closed by the server');
      },
      onerror(err) {
        console.log('There was an error from server', err);
      },
    });
  };

  const onFinish = (values) => {
    fetchData(`${getHost()}/chat?model_name=${values.model_name}&prompt=${values.prompt}`);
  };

  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="提问">
          <Input />
        </Form.Item>
        <Form.Item>
          <Space>
            <Button type="primary" htmlType="submit">
              提交
            </Button>
            <Button onClick={() => controller.abort()}>
              暂停
            </Button>
          </Space>
        </Form.Item>
      </Form>
      <Dropdown menu={{ items, onClick: menuClick }} trigger={['contextMenu']}>
        <TextArea value={value} {...sharedProps} />
      </Dropdown>
    </div>
  );
};


界面比较简陋,大家随便看一下:

前端代码还加了些小功能,比如右键支持某句话的替换和翻译,因为用了input,所以可以获取光标的位置从而把文本插入或者替换选中文本。不过还有个弊端就是没办法支持markdown输出了,这个问题暂时还不知道怎么解决,要么再添加个预览模式。

最后的最后

感谢你们的阅读和喜欢,我收藏了很多技术干货,可以共享给喜欢我文章的朋友们,如果你肯花时间沉下心去学习,它们一定能帮到你。

因为这个行业不同于其他行业,知识体系实在是过于庞大,知识更新也非常快。作为一个普通人,无法全部学完,所以我们在提升技术的时候,首先需要明确一个目标,然后制定好完整的计划,同时找到好的学习方法,这样才能更快的提升自己。

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

img

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

img

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。

img

四、AI大模型商业化落地方案

img

五、面试资料

我们学习AI大模型必然是想找到高薪的工作,下面这些面试题都是总结当前最新、最热、最高频的面试题,并且每道题都有详细的答案,面试前刷完这套面试题资料,小小offer,不在话下。
在这里插入图片描述

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/682683.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Postman接口测试】第五节.Postman接口测试项目实战(下)

文章目录 前言七、课程添加接口postman测试 7.1 课程添加接口文档 7.2 针对课程添加设计接口测试用例 7.2.1 提取测试点 7.2.2 设计测试用例 7.2.2 使用Postman进行接口测试八、查询课程列表接口postman测试 8.1 查询…

插件:Plugins

一、安装网格插件

Allegro器件角度倾斜如何回正?

Allegro器件角度倾斜,坐标含有小数点调整为45度整数倍的方法 Allegro器件角度倾斜回正的方法。 在用Allero进行PCB设计过程中,有时候由于误操作;或者刚开始器件需要非45度整数倍的角度,后又需要调整为整数倍的角度。器件角度倾斜含有小数点调整为45度整数倍的方法。 1、如…

小白学大模型:Hugging Face Tokenizer

Tokenizer介绍 在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;Tokenizer&#xff08;分词器&#xff09;是准备输入模型的关键步骤之一。Hugging Face 提供了用于各种模型的分词器库&#xff0c;其中大多数分词器都以两种风格提供&#xff1a;一种是完整的 Pytho…

使用 MDC 实现日志链路跟踪,包教包会!

在微服务环境中&#xff0c;我们经常使用 Skywalking、Spring Cloud Sleut 等去实现整体请求链路的追踪&#xff0c;但是这个整体运维成本高&#xff0c;架构复杂&#xff0c;本次我们来使用 MDC 通过 Log 来实现一个轻量级的会话事务跟踪功能&#xff0c;需要的朋友可以参考一…

三十七、openlayers官网示例Earthquakes Heatmap解析——在地图上加载热力图

官网demo地址&#xff1a; Earthquakes Heatmap 这篇主要介绍了热力图HeatmapLayer HeatmapLayer 是一个用于在地图上显示热力图的图层类型&#xff0c;通常用于表示地理数据中的密度或强度。例如&#xff0c;它可以用来显示地震、人口密度或其他空间数据的热点区域。在这个示…

springboot3 一些听课笔记(1)

文章目录 一、日志框架二、springboot 自动配置三 、springweb3.13.2 自己编写一个messageconvert3.2.2 如果我们想让其支持yaml格式呢&#xff1f; 一、日志框架 springboot底层 默认使用logbacksjf4j作为日志框架。 1、每个 starter 场景&#xff0c;都会导入一个核心场景 …

鸿蒙开发接口安全:【@system.cipher (加密算法)】

加密算法 说明&#xff1a; 本模块首批接口从API version 3开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import cipher from system.ciphercipher.rsa rsa(Object): void RSA 算法加解密。 系统能力&#xff1a; SystemCapabil…

JVM学习-Jprofiler

JProfiler 基本概述 特点 使用方便&#xff0c;界面操作友好对被分析的应用影响小(提供模板)CPU&#xff0c;Tread&#xff0c;Memory分析功能尤其强大支持对jdbc,noSql,jsp,servlet,socket进行分析支持多种模式(离线、在线)的分析支持监控本地、远程JVM跨平台&#xff0c;拥…

Android WebView上传文件/自定义弹窗技术,附件的解决方案

安卓内核开发 其实是Android的webview默认是不支持<input type"file"/>文件上传的。现在的前端页面需要处理的是&#xff1a; 权限 文件路径AndroidManifest.xml <uses-permission android:name"android.permission.WRITE_EXTERNAL_STORAGE"/&g…

代理IP类型有哪些?定义与区别

您应该对代理有了一定的了解。但是&#xff0c;代理服务器也有不同的类型。就其来源而言&#xff0c;最常见的代理服务器类型是住宅代理和数据中心代理&#xff1a; 1、住宅代理 住宅代理是 ISP 向房主提供的 IP 地址。它是与物理位置关联的真实 IP 地址&#xff0c;因此允许…

运放应用1 - 反相放大电路

1.前置知识 反相放大电路存在 负反馈电路 &#xff0c;工作在线性区&#xff0c;可以利用 虚短 概念来分析电路。 注&#xff1a;运放的 虚断 特性是一直存在的&#xff0c;虚短特性则需要运放工作在 线性区 有关运放的基础知识&#xff0c;可以参考我的另外一篇文章&#xff…

flutter as连接网易模拟器

网易模拟器下载 Mac 使用MuMu模拟器调试 Flutter开发 Android Studio 安装第三方模拟器—网易MuMu Mac 安卓Studio使用外部模拟器 Mac电脑:Android Studio 连接 MUMU 网易模拟器 Mac 上 Android Studio 链接网易 MuMu 模拟器调试 在 .zshrc 中设置 adb 二进制文件路径: …

纯血鸿蒙开发实战—如何使用画布组件Canvas绘制图形和文字,以及转圈动画的实现

本文主要通过抽奖转盘小项目讲解在鸿蒙开发中如何使用画布组件Canvas绘制图形和文字&#xff0c;以及转圈动画的实现。效果图如下&#xff1a; 首先绘制转盘的六个分区&#xff1a; drawInnerArc() {let colors [rgb(61,127,255),rgb(121,189,255)];let radius this.screenW…

运维系列.Linux下的用户管理

运维系列 Linux下的用户管理 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.csdn.net/qq_28550263/ar…

[RK3588-Android12] 关于BQ25703充电IC+CW2017电量计调试

问题描述 BQ25703充电ICCW2017电量计调试 解决方案&#xff1a; 附上dts配置文件 &i2c6 {clock-frequency <400000>;status "okay";// CONFIG_BATTERY_CW2017cw2017: cw201763 {status "okay";compatible "cellwise,cw2017";re…

数字影像产业园的三大赋能:科技、创新与无限可能

数字影像产业园作为文创产业的重要载体&#xff0c;以科技为核心驱动力&#xff0c;不断推动产业的技术革新和升级。 园区内汇聚了最前沿的数字技术资源&#xff0c;高清摄影设备、虚拟现实技术、人工智能应用等尖端科技在这里得到广泛应用&#xff0c;不仅提升了生产效率&…

如何一键批量下载淘宝高清主图、详情图、sku及视频信息

图片对电商至关重要&#xff0c;因为它们是商品展示的主要方式&#xff0c;直接影响消费者的购买决策。高质量的图片能够吸引顾客注意&#xff0c;提升产品吸引力&#xff0c;增加点击率和转化率。此外&#xff0c;好的图片还有助于建立品牌形象&#xff0c;提高客户信任度。 …

算法金 | 10 大必知的自动化机器学习库(Python)

大侠幸会&#xff0c;在下全网同名[算法金] 0 基础转 AI 上岸&#xff0c;多个算法赛 Top [日更万日&#xff0c;让更多人享受智能乐趣] 一、入门级自动化机器学习库 1.1 Auto-Sklearn 简介&#xff1a; Auto-Sklearn 是一个自动机器学习库&#xff0c;基于 Python 的 scikit…

IP黑名单与IP白名单是什么?

在IP代理使用中&#xff0c;我们经常听到黑名单与白名单两个名词&#xff0c;它们不仅提供了强大的防御机制&#xff0c;还可以灵活应对不同的安全威胁。本文将详细探讨IP黑名单和白名单在网络安全中的双重屏障作用。 一、IP黑名单和白名单定义 IP黑名单与IP白名单是网络安全中…