V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
brader
V2EX  ›  程序员

ChatGPT 的/v1/chat/completions 接口流式响应设计有点不科学

  •  1
     
  •   brader · 2023-03-07 10:26:18 +08:00 · 7444 次点击
    这是一个创建于 622 天前的主题,其中的信息可能已经有所发展或是发生改变。

    当启用 stream=true 的时候,以流响应,返回的数据大体如下:

    data: {"id":"chatcmpl-6r3B875xFqmzK9lMm8sousVO3iBN4","object":"chat.completion.chunk","created":1678101622,"model":"gpt-3.5-turbo-0301","choices":[{"delta":{"role":"assistant"},"index":0,"finish_reason":nul
    l}]}
    
    data: {"id":"chatcmpl-6r3B875xFqmzK9lMm8sousVO3iBN4","object":"chat.completion.chunk","created":1678101622,"model":"gpt-3.5-turbo-0301","choices":[{"delta":{"content":"\n\n"},"index":0,"finish_reason":null}
    ]}
    
    data: {"id":"chatcmpl-6r3B875xFqmzK9lMm8sousVO3iBN4","object":"chat.completion.chunk","created":1678101622,"model":"gpt-3.5-turbo-0301","choices":[{"delta":{"content":"作"},"index":0,"finish_reason":null}]}
    
    data: {"id":"chatcmpl-6r3B875xFqmzK9lMm8sousVO3iBN4","object":"chat.completion.chunk","created":1678101622,"model":"gpt-3.5-turbo-0301","choices":[{"delta":{"content":"为"},"index":0,"finish_reason":null}]}
    
    data: {"id":"chatcmpl-6r3B875xFqmzK9lMm8sousVO3iBN4","object":"chat.completion.chunk","created":1678101622,"model":"gpt-3.5-turbo-0301","choices":[{"delta":{"content":"一个"},"index":0,"finish_reason":null}}
    
    ...
    
    data: [DONE]
    

    每一个 event data 的 json 串,得到的 content 内容仅仅是一个字,看完真觉得浪费流量啊,是否我的调用方式不对呢?有其他的参数或调用方式来避免这种浪费吗?

    54 条回复    2024-08-27 11:49:08 +08:00
    string2020
        1
    string2020  
       2023-03-07 10:38:41 +08:00
    流量要花钱?
    brader
        2
    brader  
    OP
       2023-03-07 10:49:21 +08:00
    @string2020 更多思考的是技术方面的东西,这样 API 的传输效率不好
    orangie
        3
    orangie  
       2023-03-07 10:53:20 +08:00
    传输效率不高,但是用户体验确实很好的,追求的是响应时间更短,而不是吞吐量更高,不过感觉其他的 id 和标记可以再简化一下,一个会话用一个简单的 id 。
    string2020
        4
    string2020  
       2023-03-07 10:55:04 +08:00   ❤️ 1
    api 提供商都不在乎的东西你个外人跑来在乎。有这功夫,优化下前端,帮用户省点磁盘内存
    brader
        5
    brader  
    OP
       2023-03-07 11:00:41 +08:00   ❤️ 1
    @string2020 这和我是相关的,我们并不想暴露 token 等敏感信息,不是由前端直接调用的 chatgpt ,中间有一层我们的服务端在代理,所以这情况同样会影响到我们服务端的带宽和响应时间
    Kinnice
        6
    Kinnice  
       2023-03-07 11:05:46 +08:00
    那关闭 stream 呗
    ibegyourpardon
        7
    ibegyourpardon  
       2023-03-07 11:06:11 +08:00   ❤️ 11
    我认为恰恰是楼主想提供更好的方式和体验,才试图找到更好的方法来优化。
    而不是像有的人说的很搞笑,花不花钱,提供商在不在乎。
    ideacco
        8
    ideacco  
       2023-03-07 11:07:00 +08:00
    这可能是未来一段时间 AI 接口请求的基础“通病”了,因为之前的 ai 接口,不太依靠上下文,所以直接请求即可。而现在是非常依赖上下文的,那你怎么办?就是你想请求的问题,必须是在上线问基础上它才能回答的更好,SO ,现在有些大佬的方案是,每过一段时时间,对上面的问题进行总结(同样使用 gpt 接口)。然后删掉旧的上下文,使用总结后的上下文继续请求。
    另外需要 API 接口这边做一些中间件,比如把会话内容存在数据库中,然后前端用的时候可以中间插入或修改某些回话,就像 GPT 网页端做的那样。
    tool2d
        9
    tool2d  
       2023-03-07 11:11:50 +08:00
    我昨天还想发帖来着,就是很浪费流量。

    关键点还不是 data:这种格式,因为这种格式不是 openai 发明的,而是 firefox 发明的。

    而是 openai 服务器,无法进行任何的压缩传输!!我试了所有的压缩方法,都没有能开启文本压缩功能( content-encoding 始终不生效)。
    locoz
        10
    locoz  
       2023-03-07 11:18:04 +08:00
    当时抓包看到这返回方式的时候就感觉很蠢...明明可以分开两部分传输,却非要放在一个 json 里返回,导致流量浪费极大。只能说做术业有专攻,做 AI 的并不懂后端和网络。
    brader
        11
    brader  
    OP
       2023-03-07 11:21:20 +08:00
    @Kinnice 经测试,关闭 stream ,在提问某些问题的时候,AI 思考需要的时间太久了,响应的 20s 以上,我想用户是无法等待的
    brader
        12
    brader  
    OP
       2023-03-07 11:23:54 +08:00
    @ideacco 这里我讨论的不是上下文的问题,请求入参传递的 messages 数组上下文对话,才是你说的东西。
    这里质疑的是它的响应报文,该响应报文是单次回答的,只是流式响应的时候,拆分后数据格式太臃肿
    zhang77555
        13
    zhang77555  
       2023-03-07 11:38:37 +08:00 via Android
    就是原 API 返回的字符串结果改成了用 stream 返回,方便实现前端单个字吐出来的效果,我做 demo 的时候也是这么干的😂
    string2020
        14
    string2020  
       2023-03-07 12:01:37 +08:00
    离谱。大兄弟,传送 10b 和传送 100b 的响应时间差别有多大 有测试过?
    string2020
        15
    string2020  
       2023-03-07 12:03:13 +08:00
    服务端的带宽,我想知道你们服务端最大带宽是多少,平常用到了 1%没有?
    brader
        16
    brader  
    OP
       2023-03-07 12:09:12 +08:00   ❤️ 12
    @string2020 作为一个 5 年以上的服务端开发人员,虽然我的大部分菱角和追求都被磨灭了,但我依然很不认可你的这个心态。
    一个有一定发展历史的系统,它的 API 接口非常多,不仅仅只这一个,如果每一个接手项目的人,做东西的心态都是:这个接口 100ms 和 150ms 的响应时间有什么区别,没关系、不理他。系统多年堆积下来,整体的响应时间、并发率,是非常堪忧的
    8520ccc
        17
    8520ccc  
       2023-03-07 12:10:46 +08:00 via iPhone
    宽带影响不大吧,自己二次开发一下,然后减少返回的信息就行
    lambdaq
        18
    lambdaq  
       2023-03-07 12:13:38 +08:00
    @brader 你以为这样效率不好。。实际上,gpt 模型就是这样一字一字输出的。。
    brader
        19
    brader  
    OP
       2023-03-07 12:15:21 +08:00
    @8520ccc 目前我也是有这方面的想法,就是服务端似乎很少有像 js 解析 Event stream format 数据的扩展包,正打算自己写一个解析工具
    brader
        20
    brader  
    OP
       2023-03-07 12:16:31 +08:00
    @lambdaq 我认可这种一字一字输出的行为的,我意思是它的 API 设计,在流传输的时候,传一个字过来,附带大量其它臃肿的信息
    youthfire
        21
    youthfire  
       2023-03-07 12:25:05 +08:00
    我昨天也在看这个 stream ,发现比非流式慢很多。
    以下理解不知道对不对:
    按照官方说法,free-20RPM,credit card (48hrs)-60RPM,credit card (aft 48hrs)-3500RPM
    我现在理解是 60RPM 相当于 1s 吐一个字呗,因为它一次请求就返回一个字的内容。非流式只有在 3500RPM 才有实用价值吧。
    问 ai ,它解释非流式默认 600RPM ,那的确就是必须绑卡 48 小时后才有实用价值,否则还不如老老实实非流式了。
    另外我想说,stream=True 返回的是生成器,不加应该直接可以用 response['choices'][0]['message']['content'].strip(),但是我发现设定 stream=False 返回的也是生成器,不能取值,不知道是不是我方法不对,第一次接触这类。
    tool2d
        22
    tool2d  
       2023-03-07 12:26:49 +08:00
    @brader 其实不臃肿,你试一下用 gzip 流式压缩这些重复字符串,

    我这里 60k 左右的文本数据回答,可以压缩到 2k 之内。

    就是 openai 把这项特性关了,只打开了 chunk 传输,贼讨厌。
    tool2d
        23
    tool2d  
       2023-03-07 13:05:59 +08:00
    我又想了一下。

    既然 openai 在 http 协议上不支持压缩( accept-encoding: gzip, deflate, br ),那么干脆换个思路,让 openai 用 HTTPS 代理文本内容的时候,用 SSL 开启压缩功能(HANDSHAKE_SERVER_HELLO 里的 compression flag)就可以了。

    这样 openai 的服务器代码,也可以不需要改动。
    string2020
        24
    string2020  
       2023-03-07 13:06:34 +08:00
    装个锤子,正常开发人员都会知道带宽根本没影响。
    你自己一点测试都不做,凭空优化(不,你根本优化不了。api 的代码你都控制不了。凭空要求 openai 优化)。
    最好的情况是,openai 帮你做了优化。
    最后响应时间从 100ms 优化到了 100ms 。带宽从 1%占用优化到了 1%占用。
    你浪费了大家时间。
    otakustay
        25
    otakustay  
       2023-03-07 13:08:43 +08:00
    楼主的意思是用 stream 是好的,但没必要 stream 里每一份 data 都包一个 json ,可以每个 data 就一个内容字符串就行了
    我是认同楼主的想法的,json 重复内容太多了
    tool2d
        26
    tool2d  
       2023-03-07 13:10:29 +08:00   ❤️ 1
    @youthfire "但是我发现设定 stream=False 返回的也是生成器,不能取值,不知道是不是我方法不对"

    我这里可以正常取值,就是普通的 json 。
    devliu1
        27
    devliu1  
       2023-03-07 13:11:15 +08:00
    @brader 同意楼主观点,冗余信息过多
    string2020
        28
    string2020  
       2023-03-07 13:19:31 +08:00
    离大谱。想到了,父母让女儿找富二代的笑话。
    xingjue
        29
    xingjue  
       2023-03-07 13:35:15 +08:00
    离大谱。想到了,父母让女儿找富二代的笑话。
    vishun
        30
    vishun  
       2023-03-07 15:05:24 +08:00
    @string2020 #24 ?? 我都怀疑你是否是开发人员了?又不是只有一个用户在访问,怎么可能没有影响?
    dobelee
        31
    dobelee  
       2023-03-07 15:17:38 +08:00
    可以理解为元数据。做成长连接也得有基础协议的元数据。
    string2020
        32
    string2020  
       2023-03-07 15:21:03 +08:00
    @vishun 什么东西。这里说的带宽是指什么你懂吗?
    vishun
        33
    vishun  
       2023-03-07 16:53:37 +08:00
    @string2020 #32 说下指什么?
    string2020
        34
    string2020  
       2023-03-07 17:05:17 +08:00
    @vishun 这里说的是楼主的服务器和 openai 之间的下行带宽,和用户关系在哪?
    wqhui
        35
    wqhui  
       2023-03-07 17:42:12 +08:00
    像极了人,人说话也是一个字一个字的说(doge
    wtfedc
        36
    wtfedc  
       2023-03-07 17:52:49 +08:00
    @string2020 如果有 1000k 的用户同时用,中间做日志存储,如果再有实时分析,怎么可能 IO 没影响。带宽不是问题,你这意思是不够用就加硬件呗,反正不是自己花钱。程序员要都是你这想法,淘宝 JD 都不一定能开起来。
    string2020
        37
    string2020  
       2023-03-07 17:57:44 +08:00
    大兄弟。在这个提问中,用户就只有楼主自己,占用的带宽是服务器的下行带宽,下行带宽一般情况下免费的 1%都不会用到。你该不会是在为 openai 考虑吧?
    yuezk
        38
    yuezk  
       2023-03-07 18:01:54 +08:00
    你如果抓一下 ChatGPT 的接口,你会发现每个 data 都会重复前面的内容,还不如 OpenAI 的接口省带宽。

    ```
    event: add
    id: 1
    data: {"message":{"id":"chatcmpl-6rOOU6qBvhphYF8aL0Rw1VMitV5U8","role":"assistant","user":null,"create_time":null,"update_time":null,"end_turn":null,"weight":0,"recipient":"all","metadata":null,"content":{"content_type":"text","parts":["I"]}},"error":null}

    event: add
    id: 2
    data: {"message":{"id":"chatcmpl-6rOOU6qBvhphYF8aL0Rw1VMitV5U8","role":"assistant","user":null,"create_time":null,"update_time":null,"end_turn":null,"weight":0,"recipient":"all","metadata":null,"content":{"content_type":"text","parts":["I'm"]}},"error":null}

    event: add
    id: 3
    data: {"message":{"id":"chatcmpl-6rOOU6qBvhphYF8aL0Rw1VMitV5U8","role":"assistant","user":null,"create_time":null,"update_time":null,"end_turn":null,"weight":0,"recipient":"all","metadata":null,"content":{"content_type":"text","parts":["I'm sorry"]}},"error":null}

    event: add
    id: 4
    data: {"message":{"id":"chatcmpl-6rOOU6qBvhphYF8aL0Rw1VMitV5U8","role":"assistant","user":null,"create_time":null,"update_time":null,"end_turn":null,"weight":0,"recipient":"all","metadata":null,"content":{"content_type":"text","parts":["I'm sorry,"]}},"error":null}

    event: add
    id: 5
    data: {"message":{"id":"chatcmpl-6rOOU6qBvhphYF8aL0Rw1VMitV5U8","role":"assistant","user":null,"create_time":null,"update_time":null,"end_turn":null,"weight":0,"recipient":"all","metadata":null,"content":{"content_type":"text","parts":["I'm sorry, I"]}},"error":null}

    event: add
    id: 6
    data: {"message":{"id":"chatcmpl-6rOOU6qBvhphYF8aL0Rw1VMitV5U8","role":"assistant","user":null,"create_time":null,"update_time":null,"end_turn":null,"weight":0,"recipient":"all","metadata":null,"content":{"content_type":"text","parts":["I'm sorry, I'm"]}},"error":null}
    ```
    string2020
        39
    string2020  
       2023-03-07 18:04:51 +08:00
    (带宽费用)[!https://imgur.com/HT9oEzk]
    string2020
        40
    string2020  
       2023-03-07 18:15:03 +08:00
    阿里云都是免费的,不知道要省啥
    UIXX
        41
    UIXX  
       2023-03-07 18:32:54 +08:00
    OP 的问题可以总结为:能否省略或者压缩流式响应中的元数据。

    我的看法倾向于 8L 的观点,这个接口输出被设计成通用性更强、结构更明显的 chunk 形式,方便调用者直接使用。将其他形式的输出实现交给二次开发者。
    zhwithsweet
        42
    zhwithsweet  
       2023-03-07 18:53:01 +08:00
    支持 event source / SSE 的接口是这样的,写得时候图省事,直接把数据体写到 response 里边,出来必要的 { event, id data, retry } 别的感觉是能够压缩
    neotheone2333
        43
    neotheone2333  
       2023-03-07 21:57:42 +08:00
    楼主想要的应该是类似 pb 的那种形式
    上面有的人可能没用过除了 JSON 以外的数据交换格式
    byzod
        44
    byzod  
       2023-03-07 23:50:32 +08:00
    虽然但是,ai 答复的后端资源需求是非常高的,传输带宽只是流程上一个优先级非常低的环节,大概率不是性能热点

    正经程序员也不会在业务逻辑耗时 2000ms 的时候扭头去想办法把 parser 的时间从 2.5ms 优化到 1ms 吧,虽然这是 60%的性能提升——对于这个环节来说
    iseki
        45
    iseki  
       2023-03-08 00:07:52 +08:00 via Android
    但是这样的 API 对接起来容易
    iseki
        46
    iseki  
       2023-03-08 00:13:33 +08:00 via Android
    楼主的关注点可能是每个条目都包含了重复的信息,id object 什么的…只能说优化这个挺麻烦,得在现在这种简单的模式基础上再包装一层了
    superares
        47
    superares  
       2023-03-08 08:22:00 +08:00 via iPhone
    有没有可能一个流就是一个 token ?
    vinciarts
        48
    vinciarts  
       2023-03-08 09:22:40 +08:00 via Android
    弱弱的问一下,为什么要自己服务器转发,而不是直出啊?
    conn4575
        49
    conn4575  
       2023-03-08 13:34:39 +08:00 via Android
    我觉得 websocket 才是解决方案
    yuhangch
        50
    yuhangch  
       2023-03-08 14:05:05 +08:00
    @vinciarts #48 不想暴露自己的 token
    vinciarts
        51
    vinciarts  
       2023-03-08 14:57:41 +08:00
    @yuhangch 这样的话那直接用 versel 或者 netlify 部署就可以啊
    brader
        52
    brader  
    OP
       2023-03-08 15:46:36 +08:00
    @vinciarts 在目前 ChatGPT 的 API 采用直传 token 的授权形式的情况下,不管你采用什么方式,要么你有服务端代理来授权,要么你直接客户端访问,你隐藏的再怎么好,还是有暴露 token 的风险。
    上面只是其中之一原因,最近开始,最主要的原因还是需要代理的问题,被 GFW 墙了
    vinciarts
        53
    vinciarts  
       2023-03-09 09:36:49 +08:00
    @brader 原来如此
    nancccc
        54
    nancccc  
       82 天前
    @yuezk 这个设计真是太差了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   6201 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 02:57 · PVG 10:57 · LAX 18:57 · JFK 21:57
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.