Python使用fastAPI实现一个流式传输接口

1. 使用fastapi实现流式传输

1.1 服务端 fastapi_server.py

编写服务端代码fastapi_server.py。服务端代码主要使用了fastapi和uvicorn两个库。

#!/usr/bin/env python
# coding=utf-8
# @Time    : 2024/1/31 19:13
# @Software: PyCharm
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import time
import uvicorn

app = FastAPI()


async def generate_data():
    for i in range(1, 11):
        time.sleep(1)  # 模拟每秒生成一个块的耗时操作
        yield f"FASTAPI Chunk {i}\n"


@app.get("/stream")
async def stream_data():
    return StreamingResponse(generate_data(), media_type="text/plain")


if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=8001)

1.2客户端 (requests)

使用python编写一个客户端stream_client.py,进行流式接收。

#!/usr/bin/env python
# coding=utf-8
# @Time    : 2024/1/31 19:14
#
# stream_client.py


import requests

url = "http://127.0.0.1:8001/stream/"  # 替换为你的实际接口地址


def test1():
    try:
        response = requests.get(url, stream=True) # stream参数为True

        if response.status_code == 200:
            for chunk in response.iter_content(chunk_size=7):  # 这行很重要哦
                if chunk:
                    print(chunk.decode("utf-8"), end="")
    except requests.RequestException as e:
        print(f"Request failed: {e}")


def test2():
    try:
        response = requests.get(url, stream=True)

        if response.status_code == 200:
            for line in response.iter_lines(decode_unicode=True, chunk_size=8):
                if line:
                    print("Received SSE event:", line)
    except requests.RequestException as e:
        print(f"Request failed: {e}")


# test1()
test2()

1.3 在html中流式显示

新建一个client.html文件,放在fastapi_server.py目录下。同时修改fastapi_server.py, 增加一个BlockIterator迭代器对长文本进行切块。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Streaming Client (client.html)</title>
</head>
<body>
aaa
<div id="output"></div>

<script>
    const outputDiv = document.getElementById('output');

    // 替换为你的实际接口地址
    const url = '$FASTAPI$';

    // 使用 Fetch API 发送请求
    fetch(url)
        .then(response => {
            const reader = response.body.getReader();
            return new ReadableStream({
                async start(controller) {
                    while (true) {
                        const { done, value } = await reader.read();

                        // 如果读取完毕,中止流
                        if (done) {
                            controller.close();
                            break;
                        }

                        // 将每个块的内容添加到页面上
                        outputDiv.innerHTML += new TextDecoder().decode(value) +"<br>";
                    }
                }
            });
        })
        .then(stream => {
            // 使用 TextStream,将流连接到页面上的输出
            const textStream = new TextStream(stream);
            return textStream.pipeTo(new WritableStream({
                write: chunk => {
                    // 在这里你可以处理每个块的数据
                    console.log('Received chunk:', chunk);
                }
            }));
        })
        .catch(error => console.error('Error:', error));
</script>

</body>
</html>

服务端增加一个/web_client接口,读取上述网页内容

 

#!/usr/bin/env python
# coding=utf-8
# @Time    : 2024/1/31 19:13
# @Software: PyCharm
from fastapi import FastAPI
from starlette.responses import HTMLResponse
from fastapi.responses import StreamingResponse
import time
import uvicorn

app = FastAPI()

CONTENT = """《易经》被誉为诸经之首,大道之源,是中华优秀传统文化的总纲领,是中华民族五千年智慧的结晶。他含盖万有、纲纪群伦,是中华文化的杰出代表;他博大精微、包罗万象,亦是中华文明的源头。其内容涉及哲学、生命、自然、科学、政治、天文、地理、文学、艺术等诸多领域,是各家共同的经典。
《易经》包括《连山》《归藏》《周易》三部易书,现存于世的只有《周易》。《周易》相传是周文王被囚羑里时,研究《易经》所作的结论。"""


class BlockIterator:
    def __init__(self, text, block_size=10):
        self.text = text
        self.block_size = block_size
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index >= len(self.text):
            raise StopIteration

        block = self.text[self.index:self.index + self.block_size]
        self.index += self.block_size
        return block


async def generate_data():
    for i in BlockIterator(CONTENT, block_size=1):
        time.sleep(0.05)  # 模拟每秒生成一个块的耗时操作
        # yield f"FASTAPI Chunk {i}\n"
        yield i


@app.get("/web_client", response_class=HTMLResponse)
async def read_root():
    with open("static/client.html", "r") as file:
        html_content = file.read()
        html_content = html_content.replace("$FASTAPI$", "http://127.0.0.1:8001/stream/")
    return HTMLResponse(content=html_content)


@app.get("/stream")
async def stream_data():
    return StreamingResponse(generate_data(), media_type="text/plain")


if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=8001)

### 如何在 Flask 或 FastAPI实现大模型的流式响应 #### 使用 FastAPI 实现流式响应 FastAPI 提供了强大的支持用于构建高效、异步的应用程序接口,非常适合处理复杂的多模态数据请求。为了实现实时的数据传输和提高用户体验,在处理大型模型推断结果时采用流式响应是一种有效的方法。 当涉及到大模型推理时,由于计算量庞大可能导致长时间等待,因此可以考虑分批次返回部分结果给客户端,而不是等到整个过程结束才一次性给出答复。这不仅提升了用户的即时感知度,也减轻了内存压力[^1]。 下面是一个简单的例子来说明如何利用 Python 的 `async`/`await` 特性和 FastAPI 来创建一个流式的 API: ```python from fastapi import FastAPI, Request import asyncio app = FastAPI() @app.get("/streaming_inference/") async def streaming_inference(request: Request): async def event_generator(): for i in range(5): # 假设这是五次逐步得到的结果片段 await asyncio.sleep(1) # 模拟耗时操作 yield f"data:{i}\n\n" if await request.is_disconnected(): # 如果连接被中断则停止生成器 break return EventSourceResponse(event_generator()) ``` 这段代码展示了怎样定义一个 GET 请求处理器 `/streaming_inference/` ,它会每隔一秒向客户端推送一条消息直到完成全部五个阶段的消息发送或是检测到客户端已断开连接为止。这里使用到了 SSE (Server-Sent Events),这是一种允许服务器主动向浏览器或其他 HTTP 客户端推送更新的技术[^4]。 对于更复杂的情况比如图像识别或自然语言处理任务,则可以根据具体需求调整上述逻辑中的模拟延迟部分为实际的大规模预训练模型调用,并确保这些调用是非阻塞的方式执行以便维持良好的性能表现。 #### 使用 Flask 实现流式响应 虽然 Flask 不像 FastAPI 那样原生地强调异步特性,但它仍然可以通过一些技巧达到相似的效果。例如,借助于第三方库如 gevent 或者直接运用 Werkzeug 自带的支持来进行长期运行的任务处理并实时反馈进度给用户。 以下是基于 Flask 构建的一个简单示例,该实例同样实现了类似于前面提到的功能——即周期性地向客户端传递信息直至所有步骤完成: ```python from flask import Flask, Response import time app = Flask(__name__) def generate_data(): for i in range(5): time.sleep(1) yield 'data: {}\n\n'.format(i) @app.route('/stream') def stream(): return Response(generate_data(), mimetype='text/event-stream') if __name__ == '__main__': app.run(debug=True, threaded=True) ``` 此段脚本里我们定义了一个名为 `/stream` 的路由地址,访问这个 URL 后将会启动一个持续性的事件源通信通道,每秒钟发出一次新的数值作为回应的一部分内容。需要注意的是为了让这种模式正常运作起来,必须保证 Web 服务器配置正确并且能够处理长轮询类型的请求。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值