大模型调用编排, queue 衔接

我在调用大模型时,同时启动asyncio.create_task a, b任务,如何先缓慢流式输出a的结果,等b的结果完成后,如果a没有输出完呢,就全速输出,如果输出完了,就流式输出b的结果

太好了,这个场景 FastAPI + SSE + Queue + Event 非常适合 👍
下面我给你一份完整、可运行、工程级的示例,直接满足你之前的全部逻辑。

技术栈:

  • FastAPI
  • SSE(EventSourceResponse
  • asyncio.Queue
  • asyncio.Event

一、整体行为回顾(确保你对得上)

  • A(大模型)边生成边放入 queue

  • SSE 慢速流式输出 A

  • B 完成后:

    • 如果 A 没输出完 → A 立即全速 flush
    • 如果 A 已输出完 → 开始流式输出 B
  • 全程不阻塞生成、不丢 token


二、完整代码(直接复制可跑)

📦 依赖

pip install fastapi uvicorn sse-starlette

🧠 main.py

import asyncio
from fastapi import FastAPI
from sse_starlette.sse import EventSourceResponse

app = FastAPI()

1️⃣ A:模拟大模型【生成端】

async def llm_producer(queue: asyncio.Queue):
    for i in range(20):
        await asyncio.sleep(0.1)  # 模拟模型生成 token
        await queue.put(f"A-token-{i}")

    # 结束哨兵
    await queue.put(None)

2️⃣ B:后台任务

async def task_b(done_event: asyncio.Event):
    await asyncio.sleep(10)  # 模拟耗时任务
    done_event.set()
    return "B-result-xxxxxxxx"

3️⃣ SSE 核心:输出调度器(最关键)

async def sse_event_generator():
    queue = asyncio.Queue()
    b_done = asyncio.Event()

    producer_task = asyncio.create_task(llm_producer(queue))
    b_task = asyncio.create_task(task_b(b_done))

    a_finished = False

    # ====== A 的 SSE 输出阶段 ======
    while True:
        item = await queue.get()

        if item is None:
            a_finished = True
            break

        yield {
            "event": "message",
            "data": item
        }

        # B 未完成 → 慢速
        if not b_done.is_set():
            await asyncio.sleep(1)

    # ====== B 完成后的处理 ======
    if not b_done.is_set():
        b_result = await b_task
    else:
        b_result = await b_task
    # A 已结束 → 再流式输出 B
    for ch in b_result:
        yield {
            "event": "message",
            "data": ch
        }
        await asyncio.sleep(0.2)

    # ====== 收尾 ======
    yield {
        "event": "end",
        "data": "[DONE]"
    }

    await asyncio.gather(producer_task, b_task)

4️⃣ FastAPI 路由(SSE)

@app.get("/stream")
async def stream():
    return EventSourceResponse(
        sse_event_generator(),
        media_type="text/event-stream"
    )

5️⃣ 启动服务

uvicorn main:app --reload

三、前端测试(浏览器直接跑)

<script>
  const es = new EventSource("http://localhost:8000/stream");

  es.onmessage = (e) => {
    console.log("data:", e.data);
  };

  es.addEventListener("end", () => {
    console.log("stream done");
    es.close();
  });
</script>

四、关键点逐条解释(非常重要)

✅ 为什么 A 能“突然全速输出”

if not b_done.is_set():
    await asyncio.sleep(0.3)
  • sleep 只在 SSE 输出层
  • B 一完成,b_done.set(),sleep 立刻消失
  • queue 中积压的 token 会被疯狂 flush

✅ 为什么 不丢 token

  • 生成端(llm_producer)和输出端完全解耦
  • queue 天然背压
  • None 哨兵保证完整性

✅ 为什么 SSE 不会阻塞

  • EventSourceResponse + async generator
  • 每个 yield 就是一次事件推送

五、你可以立刻做的增强(我可以帮你)

  • 🔥 替换 llm_producerOpenAI / vLLM / Qwen stream
  • 🔥 给 SSE 加 event: delta / meta / error
  • 🔥 支持 多个 B,优先级加速
  • 🔥 客户端断开自动 cancel task
  • 🔥 改成 WebSocket 版本

你现在是 哪家的大模型 SDK?我可以直接给你接真实接口的版本。

posted @ 2026-01-31 17:17  X1OO  阅读(0)  评论(0)    收藏  举报