You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何改造mtl风格的Servant服务实现流式响应避免请求超时

实现方案

1. 调整API定义(二选一即可)

你可以直接在API中使用带自定义monad的SourceT,不需要强制使用SourceIO:

type ServiceAPI = "maintenance" :> StreamGet NewlineFraming PlainText (SourceT m Text)

如果希望API类型保持和IO绑定的SourceIO规范,也可以后续通过monad转换适配,两种方式都支持你的自定义mtl栈。

2. 改造myServer逻辑

你只需要将原来生成完整Text的逻辑拆分为逐块生成的逻辑,通过lift即可将自定义monad中的操作(日志、指标上报、配置读取等)抬到SourceT上下文里使用:

import Conduit (sourceList, yield) -- 如果你用的是conduit实现的SourceT
import Servant.API.Stream
import Data.Text (splitAt, null)

-- 辅助函数:把大Text切成指定大小的块,不需要修改核心业务逻辑可以用这个
chunkText :: Int -> Text -> [Text]
chunkText chunkSize t
  | null t = []
  | otherwise = let (chunk, rest) = splitAt chunkSize t in chunk : chunkText chunkSize rest

myServer ::
     MonadIO m
  => MonadLog m
  => MonadMetrics m
  => MonadRandom m
  => Config
  -> Client
  -> ServerT ServiceAPI m -- 对应上面的API定义就是 SourceT m Text
myServer cfg client = do
  -- 可以直接lift调用你自定义monad里的所有操作
  lift $ logInfo "开始生成流式响应"

  -- 方案A:复用原有返回完整Text的逻辑,切分后流式返回
  fullText <- lift $ getOriginalFullText cfg client -- 你原来返回m Text的业务逻辑
  sourceList $ chunkText 1024 fullText -- 按1KB大小切分返回

  -- 方案B:逐块生成不需要预先生成整个内容,性能更好
  -- forM_ [1..100] $ \i -> do
  --   chunk <- lift $ generateSingleChunk cfg client i
  --   yield chunk
  --   lift $ incrementMetric "chunk_sent" 1

你担心的MonadIO []错误完全不会出现,因为[Text]只是普通的列表数据,你是把它作为参数传给sourceList转为流,并没有把列表当作monad使用。

3. 适配SourceIO(如果API定义用了SourceIO)

如果你的API定义用了SourceIO Text,只需要在服务注册时通过mapSourceT把自定义monad的运行器套进去即可,假设你有个自定义monad的运行函数runMyMonad :: m a -> IO a:

-- 服务注册时的示例代码
mainServer :: Config -> Client -> Server ServiceAPI
mainServer cfg client = mapSourceT runMyMonad (myServer cfg client)

内容的提问来源于stack exchange,提问作者Kiko Fernandez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 02:24:01