【大模型调用编排】Task A流式输出,Task B/C/D并发执行

启动任务


一、你现在的明确需求(确认)

你要的是:

  1. Task A

    • 调用大模型
    • 流式输出
  2. Task B / C / D

    • 与 Task A 异步并发执行
    • 非流式调用
    • 并发跑
    • 结果缓存(内存即可)
  3. 输出顺序

    • Task A 先流式输出
    • Task A 完成后
    • 再把 Task B / C / D 的结果依次流式输出
  4. 接口

    • FastAPI
    • EventSourceResponse(SSE)
    • 可用 Postman 调用

⚠️ 注意:
Postman 支持 SSE(EventStream),但需要用 Send(不是 Send and Download)


二、整体架构(推荐)

┌──────────────┐
│ FastAPI POST │
└──────┬───────┘
       │
       ▼
┌──────────────────────────┐
│ stream_controller        │  ← 统一调度
│                          │
│  create_task(Task A)     │
│  create_task(Task B)     │
│  create_task(Task C)     │
│  create_task(Task D)     │
│                          │
│  await Task A (stream)   │
│  await B/C/D (result)    │
│  stream B/C/D result     │
└──────────────────────────┘

三、关键技术点

需求 方案
并发 asyncio.create_task
多任务结果收集 asyncio.gather
流式输出 async generator
SSE EventSourceResponse
顺序控制 await

四、完整可运行示例

1️⃣ 模拟大模型接口

Task A(流式)

import asyncio
from typing import AsyncGenerator

async def llm_stream_call(name: str) -> AsyncGenerator[str, None]:
    for i in range(5):
        await asyncio.sleep(0.6)
        yield f"{name} token {i}"

Task B / C / D(非流式)

async def llm_call(name: str) -> str:
    await asyncio.sleep(2)
    return f"{name} final result"

2️⃣ Task 定义

Task A(流式)

async def task_a():
    async for chunk in llm_stream_call("TaskA"):
        yield chunk

Task B / C / D(并发、非流式)

async def task_b():
    return await llm_call("TaskB")

async def task_c():
    return await llm_call("TaskC")

async def task_d():
    return await llm_call("TaskD")

3️⃣ 核心:流式调度器(重点)

from sse_starlette.sse import EventSourceResponse

async def stream_controller(payload: dict):
    # 1️⃣ 并发启动 B / C / D(不阻塞)
    b_task = asyncio.create_task(task_b())
    c_task = asyncio.create_task(task_c())
    d_task = asyncio.create_task(task_d())

    # 2️⃣ 先流式输出 Task A
    async for chunk in task_a():
        yield {
            "event": "task_a",
            "data": chunk
        }

    # 3️⃣ Task A 完成后,等待 B/C/D 结果
    results = await asyncio.gather(
        b_task,
        c_task,
        d_task,
        return_exceptions=True
    )

    # 4️⃣ 再把 B / C / D 的结果流式吐给客户端
    for name, result in zip(["task_b", "task_c", "task_d"], results):
        if isinstance(result, Exception):
            yield {
                "event": "error",
                "data": f"{name} failed: {result}"
            }
        else:
            yield {
                "event": name,
                "data": result
            }

4️⃣ FastAPI POST 接口(SSE)

from fastapi import FastAPI, Request

app = FastAPI()

@app.post("/stream")
async def stream_endpoint(request: Request):
    payload = await request.json()

    return EventSourceResponse(
        stream_controller(payload),
        media_type="text/event-stream"
    )

五、Postman 调用方式(非常关键)

1️⃣ 请求方式

  • Method:POST
  • URL:http://localhost:8000/stream

2️⃣ Headers

Content-Type: application/json
Accept: text/event-stream

3️⃣ Body(raw / JSON)

{
  "query": "test"
}

4️⃣ Postman 现象

你会看到类似:

event: task_a
data: TaskA token 0

event: task_a
data: TaskA token 1

...

event: task_b
data: TaskB final result

event: task_c
data: TaskC final result

event: task_d
data: TaskD final result

六、为什么这个方案是「工业级正确解」

Task A 与 B/C/D 真正并发

  • B/C/D 在 Task A 输出时已经跑完大半

无流式阻塞

  • Task A 的 token 不会被 B/C/D 卡住

SSE 标准

  • 前端 / Postman / 浏览器都支持

极易扩展

  • 后续你可以:

    • Task DAG
    • 条件分支
    • 多阶段 stream

七、常见坑提醒(你很可能会踩)

❌ 错误:在 generator 里 await 太久

会卡死流式输出

❌ 错误:B/C/D 直接 yield

会乱序

✅ 正确:并发算,顺序吐


八、如果你愿意,我可以继续帮你

我可以直接帮你:

  • 改成 真实 OpenAI / Azure / 通义 / Claude 流式 SDK
  • 封装成 通用 Task Orchestrator
  • 设计 多阶段 + 条件分支 LLM pipeline
  • 增加 心跳 / keep-alive / 超时取消

你可以直接说下一步你想要哪一个。

posted @ 2026-02-01 09:46  X1OO  阅读(0)  评论(0)    收藏  举报