Effect 流式 fallback 会把两家的输出拼在一起吗?
我拿一个先吐出「你好」再断开的流做第一家,第二家是完整的「你好!」,跑完 fallback,下游收到的是 你好你好!。
会拼在一起,这是默认行为。 要避免,给 Stream.withExecutionPlan 传 preventFallbackOnPartialStream: true,但它只阻止跨步骤降级,同一步内按 attempts 重试仍会从头重新输出。
ExecutionPlan 是 Effect 里把「先试 A,不行换 B」写成一个值的机制,基本写法见 Effect 怎么把多供应商 fallback 写成降级链。Stream.withExecutionPlan 和 Effect 版本参数一致,只多了这一个选项。流式场景下它很关键,因为失败可能发生在已经吐出一半 token 之后。下面基于 effect 4.0.5,这是 unstable 特性。
默认会发生什么?
先看一个能跑的例子:
import { Context, Effect, ExecutionPlan, Layer, Stream } from "effect"
class Llm extends Context.Service<Llm, {
readonly tokens: Stream.Stream<string, string>
}>()("app/StreamingLlm") {}
const flaky = Layer.succeed(Llm, Llm.of({
tokens: Stream.concat(Stream.make("你", "好"), Stream.fail("connection reset"))
}))
const stable = Layer.succeed(Llm, Llm.of({ tokens: Stream.make("你", "好", "!") }))
const tokens = Stream.unwrap(Effect.map(Effect.service(Llm), (llm) => llm.tokens))
const plan = ExecutionPlan.make({ provide: flaky }, { provide: stable })
const mixed = Stream.runCollect(Stream.withExecutionPlan(tokens, plan))
const strict = Stream.runCollect(
Stream.withExecutionPlan(tokens, plan, { preventFallbackOnPartialStream: true })
)
Effect.runPromise(mixed).then((r) => console.log(r))
Effect.runPromiseExit(strict).then((r) => console.log(r._tag))实际输出:
Failure
[ '你', '好', '你', '好', '!' ]第二行是默认(mixed):下游先收到第一家的「你好」,再收到第二家完整的「你好!」,拼成 你好你好!。给用户逐字展示的聊天界面,这是错的。
preventFallbackOnPartialStream 解决了什么?
设置 preventFallbackOnPartialStream: true 后,已输出过元素的步骤失败,整个流直接失败。上面输出里的第一行 Failure 就是 strict 的结果。
代价是你放弃了这次降级:用户拿到的是一个失败,而不是第二家的完整回答。要不要在失败后由调用方整体重发,是上层的决定。
同一步内的重试还会重复输出吗?
会。这个选项只阻止跨步骤降级,同一步内按 attempts 重试仍会发生,并从头重新输出。我用 attempts: 2 加此选项试过,下游看到两遍开头元素才失败。
要避免重复,有两个办法:把有部分输出风险的步骤 attempts 设为 1,或在下游缓冲到完整消息再放行。
下游提前停止拉取,算失败吗?
不算。文档写明:下游提前停止拉取(如计划外的 Stream.take)时,被截断的尝试报告 AttemptSuccess,因为是消费者停了。统计失败率时别把它当成异常。观察事件的方式见 ExecutionPlan 怎么知道最后是哪个供应商答的。
版本是 effect 4.0.5。示例和上面的输出是我用模拟 Layer 跑出来的,preventFallbackOnPartialStream对跨步骤降级的阻止、对同步骤重试无效(attempts: 2的情形)也都是我自己跑过的。下游提前停止时报告AttemptSuccess是文档写明的行为,我没有单独验证。没有接真实供应商的流式接口。