普通视图

发现新文章,点击刷新页面。
今天 — 2026年4月21日未分类

ChatGPT Image-2 上手:杀死比赛!设计师这下子真的可以解放双手了

2026年4月21日 16:18
这篇文章介绍了 ChatGPT Image2 在中文设计场景中的实际表现,重点展示了其在表情包制作、产品宣传图生成、旧海报改版以及局部改字等任务中的高可用性。作者通过多组案例认为,该模型在中文文字准确率、版式逻辑、细节还原和风格切换上都已接近可直接商用,许多原本需要设计师数小时甚至数天完成的工作,如今一句提示词即可完成。文章最终认为,除高度定制和批量化场景外,大多数平面设计流程都可能被 AI 重构。

Indent Is All You Need

作者 est
2026年4月21日 13:09

There's an interesting debate around whether "Bash is all you need" for AI agents. Claude Code's Thariq Shihipar argues that LLMs may never be particularly good at Bash. The reasoning is both practical and theoretical: complex Bash commands often break on nested quotes, parentheses, and escapes. Even GPT-5.4 struggles with deeply nested inline Bash calls, and some engineers have resorted to wrapping binaries into microcommands so the model only outputs the inner command, achieving near-perfect reliability.

The theory behind this is rooted in formal language classes. Bash's quoting and parentheses matching form a Dyck-k language problem, a type of task that requires maintaining a stack of arbitrary depth. Standard Transformers are in the TC0 complexity class, which makes deep nesting and parity tracking inherently challenging. Python, by contrast, is almost Transformer-friendly by design: each line's indentation implicitly encodes block depth. This "outsources" state tracking to the syntax itself, effectively converting a potentially hard nesting problem into something the model can handle token by token. That may explain why LLMs have excelled at Python generation from early versions, despite struggling with even basic arithmetic.

Practically, this explains the patterns people see: nested Bash commands are error-prone, while Python functions with proper indentation work reliably. YAML, Markdown, and other indentation-heavy formats behave similarly. Many people say that Markdown math formulas and JSON/XML often cause errors because of brace/bracket mismatches and escapes. Bash mistakes, on the other hand, can be catastrophic, especially when used in agent frameworks that make the AI directly invoke commands.

If we accept that LLMs are "state-tracking challenged," our choice of formats must evolve toward "line-local" state:

  • JSON/XML: High-risk. Every { is a debt that must be paid with a } 50 tokens later.
  • TOML: Superior for AI because it is flat. A section header [header.subheader] anchors the state for the following lines, requiring zero long-distance nesting memory.
  • Markdown/LaTeX: This explains why even the best models still hallucinate unrenderable LaTeX. The moment a formula requires deeply nested curly braces, the Dyck-k problem strikes, and the model "forgets" to close a bracket.

To verify this, one could conduct a simple "Indentation Test" experiment: ask a SOTA model to generate C++ code in two scenarios & then compare accuracy:

  • Standard C++ with mandatory indentation and newlines.
  • Minified C++ on a single line where indentation is forbidden.

The divergence in error rates as the nesting depth increases would likely prove that for AI, the "indentation" is the logic.

Ultimately, while Bash is a powerful glue, it is a treacherous foundation for autonomous agents. If we want reliable agents, we should favor languages and formats that offload state into the context.

Indent is all you need.


Translated from Zhihu 胡一鸣 & edited by ChatGPT

容器部署 ClickHouse

2026年2月11日 08:00
1. ClickHouse 单节点 配置环境变量 1 2 3 4 5 6 7 8 export CONTAINER_CLI=nerdctl export IMAGE=clickhouse/clickhouse-server:24 export CLICKHOUSE_INSTANCE_NAME=clickhouse export CH_DATA=/data/ops/clickhouse/$CLICKHOUSE_INSTANCE_NAME mkdir -p $CH_DATA/data $CH_DATA/log export CLICKHOUSE_PORT=9000 export CLICKHOUSE_USER=default export CLICKHOUSE_PASSWORD=xxxxxx 启动服务 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $CONTAINER_CLI run -d \ --name $CLICKHOUSE_INSTANCE_NAME \ --restart always \ --network host \ --ulimit memlock=-1 \ --ulimit stack=67108864 \ --ulimit nofile=1048576:1048576 \ --memory-swappiness=0 \ --cap-add=SYS_NICE \ --cap-add=SYS_RESOURCE \ -v $CH_DATA/data:/var/lib/clickhouse \ -v $CH_DATA/log:/var/log/clickhouse-server \ -e CLICKHOUSE_USER=$CLICKHOUSE_USER \ -e CLICKHOUSE_PASSWORD=$CLICKHOUSE_PASSWORD \ -e CLICKHOUSE_PORT=$CLICKHOUSE_PORT \ $IMAGE 测试连接 1 $CONTAINER_CLI exec -it $CLICKHOUSE_INSTANCE_NAME clickhouse-client --host 127.0.0.1 --port $CLICKHOUSE_PORT 打印交付结

容器化部署 Redis

2026年2月11日 08:00
1. Redis 主从模式 配置环境变量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 export CONTAINER_CLI=nerdctl export IMAGE=redis:7 export REDIS_INSTANCE_NAME=redis-instance export REDIS_MASTER_INSTANCE_NAME="${REDIS_INSTANCE_NAME}-master" export REDIS_REPLICA_INSTANCE_NAME="${REDIS_INSTANCE_NAME}-replica" export REDIS_PASSWORD=xxxxxx export REDIS_MASTER_PORT=6379 export REDIS_MASTER_IP=10.0.0.1 export REDIS_REPLICA_PORT=6380 export REDIS_DATA=/data/ops/redis/$REDIS_INSTANCE_NAME mkdir -p $REDIS_DATA/data $REDIS_DATA/log 启动主节点 1 2 3 4 5 6 7 8 9 10 11 $CONTAINER_CLI run -d \ --name $REDIS_MASTER_INSTANCE_NAME \ --restart always \ --network host \ --ulimit memlock=-1 \ --ulimit stack=67108864 \ --ulimit nofile=1048576:1048576 \ --memory-swappiness=0 \ -v $REDIS_DATA/data:/data \ -v $REDIS_DATA/log:/var/log/redis \ $IMAGE redis-server --port $REDIS_MASTER_PORT --requirepass $REDIS_PASSWORD 应该可以这样的日志: 1 Ready to accept connections tcp 启动

雨季又来

作者 laixintao
2026年4月21日 12:14

「未来两周受季风交替影响,本地多数日子的下午,预计会出现短暂雷阵雨,有几天的最高气温可能达到35摄氏度。未来两周全岛降雨量,预计接近常年平均水平。多数下午,部分地区将出现短暂雷阵雨,有时可能持续至傍晚;也可能有几天降雨不多。」联合早报这么说。最近会下雨已经出现在了新闻上,而不是天气预报上。

春季到了,太阳跑到了赤道的北边。我住的房子朝北,每天起床阳光会射进屋里,晒得玻璃发烫。

高温伴随着雨季,雨水降下来马上又被蒸发,每一天的空气都湿漉漉的。有时候太阳还在天上,没有什么乌云,竟然也下起来雨。

除湿机派上了大用场。每天可以从空气中吸出来两桶水。

说到除湿机,我买的 Novita 牌子,这个公司曾经把自己的产品线全部升级了一遍,从空气净化器到滤水器,所有的型号后面加上了一个 plus,和之前唯一的区——就是必须用他们自己生产的滤芯,售价高昂,而且滤芯到期之后机器会强制停止工作,必须更换。

但是之前非 plus 型号的滤芯还是在售,并且无法在 plus 上使用。用 Shopee 搜出来,非 plus 居然还排在前面,我就上当了,白白浪费了钱,得重新买。

真是气人。

2026.4.20

作者 Haotian Zheng
2026年4月21日 08:55

又是一个周一,原本今天早上想要请假的,后来想说还是去好了。到公司之后熟悉了一会自己上周写的东西,然后就午饭时间了,边吃饭边给 Expedition 下单车灯灯泡。简而言之,Expedition 毕竟是个 14 年生产的老车,灯罩老化严重,雾蒙蒙的,有些地方也有指甲大小的裂痕,估计是上任车主在内华达开的时候撞到小石子了还是怎样。我之前自己用砂纸打磨上清漆也不怎么有帮助,每次都是一个月后就又到觉得很雾蒙蒙的状态。在 Cayman 送去修 Parking Sensor 后我的主力车变回了 Expedition,因此愈发想要把灯给办了,然后差不多一周前在 eBay 上买了一套灯罩送安装好的灯泡,自己在一个周三临近下班的下午快进快出地在住处的侧边停车位上给换上,瞬间干净了。然后今天早上去公司的时候发现中控台左侧转向指示灯闪好快,等到公司停车场下来后对着车灯看了看,原来是转向灯挂了。因此在公司上班时间三心二意地下单了灯泡,觉得可能是 eBay 的卖家给我了个垃圾灯泡烧了,我亚马逊 same day delivery 个 SYLVANIA 4157 换上去就行。

然后就是平淡的一个周一,老板和我开了个 1 on 1,然后上班的时候收到了 Tim Cook 宣布离任换成 John Ternus 的消息,然后开了个组会,然后我四点多就开车跑了。回家路上已经开始下毛毛雨,到家之后就把工具拿出来,打开发动机盖开始拆螺丝,把灯罩卸下来一看,发现原来不是灯泡的问题,也不是连接线的问题,而是灯罩里用来固定灯泡的灯座松了,卡不住灯泡,我住的小区又有两个臭名昭著的大号减速带,估计是之前把灯泡卡座震动松了,然后我想再卡上去发现卡不住了。只好回到屋子里去找换下来的老车灯,然后从上面卸下来福特原厂的灯座,再换上刚到的 4157 灯泡,旋转到新的灯罩里,算是修好了。然后发现这个左侧灯的示宽灯似乎也是坏的,不知道是不是一样的问题,但这个时候雨已经要下大了,考虑到自从买了这个车示宽灯从来就没好过,想说作罢,先赶紧回屋子再说,过会还要和朋友吃饭,晚上还要打羽毛球,自己还要收拾下,找下球拍,示宽灯这东西等这几天下完雨再说。

因此我就在这里敲博客,倒没有什么“eBay 卖家卖我垃圾”之类的感叹,只是突然有点乡愁了,因为早上和 eBay 卖家发站内信说他们东西坏了的时候发现卖家地址是个国内常州的,然后我作为一个中国人在美国买中国产的灯罩,但对于常州这个生产商来说我只是又一个在 eBay 上买外贸东西的老外而已。我和他发客户支持的消息用英文,国内早上他们的客服醒来后估计也会用英文回我,我夹在两个世界的夹缝里,不被任何一方承认,我是一个既不彻底在国内,也不彻底在国外的人。

然后前几天在停车位上换灯罩的时候,也在揣揣不安,怕邻居觉得我又在停车位上搞东西,因为之前有个周末拿钻头和砂纸打磨车灯的时候旁边的邻居左看右看的,再加上这个小区 HOA 连在停车位洗车都不让,给我一种我来到了假的美国的感觉。来硅谷之前以为有所谓的车库文化,苹果和一众公司的起源都来自车库里捣鼓东西,然后真来了 settle down 后发现车库里修东西会被中年华人邻居侧目而视,小区里没有人会在停车位上修东西,所有人都开着他们的耐用丰田 Sienna 接送小孩上下学,或者自动驾驶 Tesla 去科技公司上班,有问题直接扔去修车厂,就像是我把 Cayman 扔回 Porsche Fremont 一样。因此,即使抛去国界和身份认同,仅仅只说在湾区的生活,我仍然感到异样的错位,似乎我就不应该存在于这个地方一样。

北京机器人半马冠军竟是手机厂商,荣耀凭什么包揽前三?

2026年4月21日 08:45
清晨的北京亦庄赛道,左侧成群人类跑者,右侧多台形态各异的机器人并列起跑,发令枪响瞬间,城市道路与科技氛围同框,羊皮纸,钢笔彩色手绘的统一风格。

2026北京机器人半马,一个造手机的公司跑赢了所有传统机器人公司。这到底是一个什么故事?

北京机器人半马:手机公司为何成了最大赢家?

夜间封闭测试道路上,一台人形机器人在路灯下独自试跑,工程车与路边围栏隐约可见,远处工程师观察记录,呈现赛前反复夜跑测试的场景,羊皮纸,钢笔彩色手绘的统一风格。

4月19日,也就是周日,北京亦庄的机器人半马又跑起来了。最近一段时间,北京机器人半马的消息非常多,因为前面已经做了很多次夜跑测试,就是在晚上不影响交通的情况下,让机器人上街跑一跑,做一些测试,也有很多视频传出来。毕竟这确实还是一件挺好玩的事。

4月19日早上7点半,北京亦庄鸣枪开跑,1.2万名人类跑步者和300台机器人在同一个赛道一起出发。要记住这一天,这是第一次机器人跑半马跑得比人快。机器人的冠军成绩大概是50分钟上下,之所以说“上下”,后面再解释。

这是第二届北京机器人半马,去年已经跑过一次。我还专门做过一期节目,介绍了一些参赛公司。去年介绍过的有些公司今年也参赛了,但已经名落孙山。最有趣的是,这次跑得最快的竟然是手机公司,不是机器人公司,不是宇树科技,也不是去年的冠军天工。

比赛结果:荣耀包揽前三,自主导航成关键

领奖榜单前,三台标注不同队名的荣耀机器人占据前三位置,旁边另一台更快却被标记为遥控模式的机器人被划去名次,画面强调规则与成绩反转,羊皮纸,钢笔彩色手绘的统一风格。

如果按照参赛队来算,前三名都是手机公司,全都是荣耀。如果按照总成绩来说,荣耀第一,第二名是宇树,第三名是天工,第四名是腾讯。至于去年的亚军、我特别喜欢的松延动力那个小机器人,今年基本上没什么名次。至于智辉君的智元科技,依然像去年一样没有参赛,他们去做别的事情了。

那一个做手机的荣耀,怎么就包揽前三名了呢?因为每个公司都可以用自己的机器人报多个队伍。荣耀其实报的不止三个队。最先冲线的机器人其实也是荣耀的,叫“赤兔队”,但那个机器人是遥控的。这次比赛规定,遥控机器人需要在原始总成绩上乘以1.2,所以它就没有名次了。

真正进入前三的,是荣耀的“齐天大圣队”“雷霆闪电队”和“星火燎原队”,这三支都是自主导航机器人

自主导航的意思就是不需要遥控,它完全可以依靠头部传感器跑完全程。而且现在这样的机器人,已经不现实再让人举着笔记本在后面追着跑了。去年冠军天工跑了两个多小时,还可以让工程师举着笔记本在后面追着跑;但今年已经超越人类了,它跑得比人类冠军还快。

你不能要求机器人达到人类马拉松破纪录的水平,那也不现实;而且机器人会越来越快,因为电机毕竟是机器,人是不能跟它比的。机器跑得比人快,本身就是一件正常、符合逻辑的事情。所以也不能要求程序员或者工程师举着笔记本跟着它跑。

荣耀“闪电”机器人:不像人,但更适合跑

荣耀“闪电”机器人侧视特写,夸张巨大的双侧髋关节电机清晰外露,上半身轻巧无手臂,呈现像坐姿冲刺般的奔跑姿态,突出非人形却高效的机械结构,羊皮纸,钢笔彩色手绘的统一风格。

荣耀使用的机器人叫“闪电”。这个机器人其实长得不太像人,在髋关节的位置各有一个巨大的电机,一边一个。这个机器人基本上是“坐着跑”的。人是站着跑,或者身体前倾跑;机器人为什么像坐着跑?因为它只需要用一个特别大的电机把大腿抬起来,就可以往前冲。

而且这个机器人没有手,上半身相对比较轻,看起来就是两个髋关节带着两个大电机在跑。

为了参加这次半马,这个机器人专门做了液冷改造。机器人想跑得快,第一电机要大,第二电机要能持续散热。靠风冷肯定不行,一定要上液冷。所以荣耀做了一个很强的液冷散热系统。后面还有更奇葩的散热案例。

荣耀为什么能赢?核心不是“造机器人”,而是工程能力

深圳式硬件工作台上铺满电机、线路板、散热管路与机器人关节零件,几位工程师围着拆解图纸协作调试,背景隐约是密集供应链工厂景象,羊皮纸,钢笔彩色手绘的统一风格。

那一家手机公司为什么突然在这件事上发力?腾讯有的是钱,愿意玩我们还能理解,荣耀怎么就走到这一步了?这就值得探讨了。

荣耀为什么能赢?首先是手机工程能力。在中国,造手机这件事已经被彻底研究明白了。能把手机造出来,设计其他机器人、各种控制系统,其实也没有太大问题。其次是中国整个电器制造能力。无论在华强北、深圳还是珠三角,想做出各种各样的电器,本身就不是特别困难的事。甚至他们还可以自己设计电机。荣耀这个机器人上的电机,就是他们自己设计的。

以前我去珠三角跟一些做电器的厂商沟通时,他们就说,你要做一个电器,如果里面最核心的部件不是自己做的,那就没有太大价值。荣耀这个机器人上的电机是自己做的,液冷系统虽然是配别人家的,但能跟自己的电机完美匹配起来,也并不容易。

还有一个重要背景,就是中国电动车产业的能量在外溢。内卷到一定程度以后,各种制造能力、各种配件能力、各种人才,都会向外溢出。所以现在出来做机器人,这件事本身的难度已经没有那么大了。

机器人真正难的,不是腿,而是“大脑”

工厂流水线旁,一台上半身像人下半身是轮式底盘的机器人在工位上重复上料下料动作,传送带上摆着手机零件箱,体现“站满8小时干活”的实用场景,羊皮纸,钢笔彩色手绘的统一风格。

机器人真正难在哪里?第一个是“大脑”。也就是任务规划能力,这才是难点。到目前为止,中国这些机器人团队,不管是荣耀、宇树、智元还是天工,这件事都还没完全搞定,或者说做得都比较一般。这里面稍微强一点的是智元。

智元现在已经能在上海一些手机厂商那里上工厂、上生产线了,不是去造机器人,而是去干活,站满8小时。他们已经可以在一个流水线工位上做上料、下料,把8个小时干完。这个事情其实比跑马拉松更有意义。

不过,智元那个机器并不是严格的人形,上半身像人,下半身是轮子。其实在生产线上,这样的机器可能更有意义。人形机器人主要是为了克服复杂场地环境。比如这次马拉松就不是一直在平地上跑,它有坡路、有台阶,在公园里还有一小段越野,这种环境轮式机器人搞不定。但到了工厂里,就没必要让它长腿了。

腿这个东西其实不太科学,稳定性差、能耗高,所以工厂里直接上轮子更合理。

真正进入实际应用的机器人,除了刚才说的智元这种可以在流水线上上料下料、连续干满8小时的之外,还有一些进入家庭、做人员照护的机器人。那种机器人底下其实也是轮子,上半身长得比较像人。

荣耀的技术亮点:液冷、电机、换电

比赛补给区内,工程师双手迅速为机器人背部更换大尺寸电池模组,旁边透明剖视效果展示液冷管路与一体化关节电机,计时牌停在10秒附近,羊皮纸,钢笔彩色手绘的统一风格。

荣耀现在的核心技术,全部是在消费电子领域里积累出来的。它的液冷散热系统,换热量是每分钟4升,也就是每分钟有4升液体在管子里循环。完赛以后,电机依然是冰凉的,这就是手机散热技术的一次迁移。

而且它是一体化关节电机,完全全栈自研,关节扭矩可以达到200牛米。再加上10秒级插拔换电,这个能力很强。如果没有专门设计,换电会很慢。因为机器人跑起来震动非常大,电池不能掉出来,而且电池还很大。你说做个超大电池,21公里一次都不换行不行?不行,电池会太重。做轻一点、做小一点,就得多换几次。

荣耀的这个机器人中间只换了一次电,在10.6公里处,10秒搞定。

腾讯为什么输?不是跑不快,而是换电太慢

那倒霉蛋是谁?腾讯。它跑得最快,所有机器人里都没有它快,但名次并不是最高,总成绩只排第四。为什么?就是换电池不行。它需要换两到三次电池,而且每次换电都要一到两分钟,光换电时间就比荣耀多了3到5分钟。

这次比赛设置了几个换电站,在换电站里换电池的时间,也会计入总赛程,不会给你扣掉。如果你在换电站外面换电池,比如跑着跑着突然没电了,那还要额外扣时间。所以这个比赛不光看谁跑得最快,还看整个工程设计谁做得最好。这一次,荣耀的工程设计肯定是最好的。

马拉松比什么?不是极限速度,而是持续输出与散热

一台参赛机器人跑动中背负液冷泵和管路,旁边用分解式画法对比关节发热与冷却循环,赛道边电子计时屏显示高速完赛时间,突出持续输出与散热压力,羊皮纸,钢笔彩色手绘的统一风格。

为了让这些机器人参加半马,很多团队也做了不少奇葩设计。比如散热,就是机器人跑马拉松里最痛苦的一件事。21公里,跑得比人快算本事吗?其实不算。你弄个汽车、装个轮子,怎么都比人快。但21公里真正考验的是什么?是稳定性,是持续输出,是散热不烧机器,这才是真正的考验。

冠军大概50分钟左右完成比赛,它这些关节电机要持续工作,所以散热非常重要。各个队伍都拿出了五花八门的方案。荣耀是液冷,背部背了一个液压泵,供应商是华科冷芯悬浮液冷泵。现在很多电竞游戏主机里也会用这种液冷泵。

第三名天工机器人,使用的是关节导热加整体热仿真方案。宇树科技去年其实也有机器人参赛,但不是官方自己来的,是别人拿着宇树的机器人参赛。今年则是宇树官方亲自下场,也改了机器人,做了液冷散热。气冷根本搞不定这种场景。

当然,还有很多人用宇树那台9万9的机器人去参赛。实际上,这次跑得最多的机器人还是宇树的,因为很多人买了他们家的机器人,自己组队参赛。但宇树官方用的,应该是更贵、而且经过专门改造的版本。

比赛现场的“奇葩设计”:冰袋、喷雾、气囊全上阵

换电区一片手忙脚乱,工程师提着暖壶往机器人背部灌冰块,另一人拿喷雾朝关节猛喷,还有人用湿巾擦拭机身,旁边一台绑着气囊的机器人等待上场,画面热闹又滑稽,羊皮纸,钢笔彩色手绘的统一风格。

还有一个特别好玩的机器人,是上海团队“半醒科技”的。他们最有江湖气,这个机器人背上直接背了一个冰袋。中间换电池的时候特别逗,我看到一个工程师冲上来,举着暖壶往后面倒冰块,哗啦哗啦把冰块灌进去;一边倒冰块,前面还在换电池;另一个工程师则拿着降温喷雾,往机器人的关节上喷;还有人拿湿巾不停地擦,因为用酒精湿巾擦也可以降温。

不过他们最后没跑出成绩,因为电池不行,跑三四公里就要换一次,最后一共换了7次电池,所以成绩并不好。

还有一些机器人专门加了气囊,因为怕摔,在身上绑了气囊。

这次还有一些比较有意思的机器人。比如有一个叫“小派机器人”,很小,小短腿,头上扎两个发髻,手里还捏着个奶瓶,反正开心最重要。还有一些社牛机器人,跑着跑着中间给大家跳个舞。因为很多人其实就是拿着宇树9万9的机器人来跑,这种机器人本身跑不快,那就干脆让大家一起开心一下。

荣耀为什么要做机器人?背后是转型压力

一间会议室里,桌上同时摆着智能手机、机器人零件和上市文件,几位高管面前是写着“智能终端”的战略白板,窗外隐约是资本市场与工厂双重意象,羊皮纸,钢笔彩色手绘的统一风格。

讲回这次冠军荣耀。一个造手机的厂,为什么要这么费劲做机器人?原因也很简单。荣耀是怎么来的?当年美国制裁华为,华为就把手机业务里的一部分拆出来,单独发展成荣耀。但后来华为又行了,自己做的7纳米芯片重新起来了,那么荣耀的位置就变得很尴尬。

荣耀分拆出来以后,背后其实有大量国资,很多地方政府基金直接投进去了。华为又恢复以后,荣耀的定位就很麻烦。原来大家说爱华为,现在变成爱荣耀也可以;可华为又回来了,那荣耀怎么办?去年荣耀其实已经把上市需要准备的事情基本做完了,辅导也结束了,但到现在都没有上市。

原因也很简单:定位尴尬,销量也在下滑,特别是在存储成本上升以后,竞争力更弱了。

在这样的情况下怎么办?它需要找到新的出路。这个新出路,就是从手机厂商转成“智能终端厂商”,而机器人就是他们选中的众多赛道之一。现在机器人夺冠,可能会对它未来上市带来很大帮助。

当然,上市以后也不是说机器人就能大卖。我相信未来真正能把机器人卖起来的,可能还是宇树科技、智元科技、优必选这种传统机器人厂商。荣耀背后有这么多国资,它需要这个噱头去上市,上完市以后把钱融回来。至于以后这个机器人到底能不能卖,现在还不知道。

因为现在这个“闪电机器人”已经完全不像人了。你把它放到店里,能不能招揽顾客,能不能让顾客因此来买手机,这件事很难说。这个机器人太丑了,除了跑步之外,估计也干不了别的。因为它连手都没有,机械臂那个位置就是个尖,压根没有连接手部的任何设计。所以它就是为了跑步而造的。如果这一次荣耀最后上不了市,那这个机器人也就白造了,后面没有太大意义。

机器人跑马拉松,到底有什么意义?

下一个问题是,很多人会说,中国机器人是不是很牛,是不是遥遥领先了?当然也有一些人会问,跑马拉松有什么用?要跑得快你上汽车不就行了,为什么非得上机器人?这件事到底有什么意义?

机器人跑马拉松,意义其实还是很重大的。美国那边现在更多在研究机器人的“大脑”,这当然也很有意义。但中国做机器人马拉松,它的意义在哪里?

  1. 大家全民娱乐一下,开心一下总不是坏事。
  2. 当大家看到机器人在马拉松赛道上跑的时候,就会逐渐降低机器人进入社会、进入工矿企业、进入家庭的心理障碍。

比赛现场是人和机器人分开跑的,一边是人,一边是机器人,绝对不能混在一起,这点一定要注意。因为机器人现在脑子还不太行,如果跟人混跑,很容易造成人员受伤,所以必须隔离开。

当大家看到机器人真的能跑,就会开始接受:机器人未来进入社会是可能的。经过这种马拉松式的测试,大家也能看到,它可以在人的道路上奔跑,可以做简单越野,可以上坡下坡,它的关节和基础零部件也都经受住了考验。这说明相关技术正在逐渐成熟。

当然,不是说现在跑得最快的这些机器人,以后就能直接进家庭。这些机器人除了跑步之外,没有别的用途。但对于其他公司来说,当他们现在想做机器人、想选择液冷系统、想选择关节电机时,就知道该去找谁买了。因为这些设备都是经过考验的,都是能跑完21公里的。

虽然我们未必需要它在家里跑21公里,也未必需要它50分钟跑完半马,但在正常工况下,比如在店里做导购,或者进工厂打螺丝,这些关节和基础零部件已经够用了。

这场比赛真正展示的,是中国机器人产业链

而且这种比赛还会一次一次办下去。上一次比赛只有30多支队伍,这一次已经有100支队、300台机器人。规模会逐渐扩大。中国先不说机器人“大脑”和整机,至少在机器人零部件这个角度上,已经向全世界展示了我们的能力。

以后谁想做机器人,就可以买我们的液冷系统、电池、动力关节,我们能给你拼起来,而且还能不断通过比赛,把这些零部件的性能和可靠性推向极限。至少跑21公里没问题,而且还能跑得这么快。明年一定会更快。

像极了早期汽车比赛:技术路线在实战中被筛选

画面左右对照,左边是1895年法国泥土道路上的早期汽车比赛,蒸汽汽车抛锚、汽油车继续前进;右边是现代机器人马拉松赛道上多种技术路线竞逐,形成跨时代呼应,羊皮纸,钢笔彩色手绘的统一风格。

这个历史,其实跟最早的汽车比赛很像。最早的汽车比赛是1895年在法国举办的,从巴黎开到波尔多,再从波尔多开回巴黎,全程1200公里。那个时候参加汽车比赛的各种技术路线也是五花八门。当时很多参赛车是蒸汽机的,结果第一天就全趴窝了,压根完不了赛。汽油机则在那次比赛中彻底胜出。当时其实也有电动车,但最后大家发现还是汽油机更可靠。所以后来很长时间里,汽车主流还是汽油机。

现在的人形机器人马拉松比赛,跟当年第一批汽车比赛是很像的:一方面让普通民众接受,大家看得开心;另一方面把整个设计推向极限。就像现在的汽车比赛,比如F1方程式,那个车也不能直接上街跑,但它会不断把整个汽车工业的技术极限往前推。机器人比赛以后也会一次次办下去。

普通人应该关注什么?

所以,普通人应该看什么?机器人马拉松不是比谁跑得更快,也不是看机器人能不能跑过人。机器人跑过人,这太正常了,毕竟那是机器。这次真正值得关注的是,手机厂商正在重新定义机器人赛道

而且荣耀这次的成功,其实是在告诉大家:中国所有的零部件生产都已经准备好了,大家都可以冲上去制造自己的机器人了,这才是真正有意义的地方。

这一回,荣耀、小米、华为这些公司,理论上都可以把手机工程能力、散热结构能力、供应链能力迁移到机器人上。全世界想做人形机器人的人,都可以到中国来寻找代工厂、寻找供应链、寻找零部件。

机器人现在不光能走,还能跑。能跑的意义不是快,而是稳定性。它可以非常稳定、持续地输出21公里,这一点非常重要。而且这个成绩接下来几年还会快速提升。去年的最好成绩是2小时40分钟,今年已经到了50分钟,一年快了110分钟。明年大概率还会继续提升。

这个速度上的进步不是噱头,而是整个中国机器人产业链在材料、算法、供应链上的一次集体冲刺,等于整个体系都在上升。以后美国人可以继续去做机器人大脑,当然我们自己也在努力做大脑;而下面这些组件,我们已经可以拼出来了。

真正领先的,不只是单个机器人,而是整条产业链

中国现在展示的是一种“兼容机器人”的未来,就像当年的兼容机一样。所谓兼容机,就是有了一个基本配置以后,大家就可以把它攒起来。中国现在是在告诉全世界,我们也可以这么干。你需要电机、需要外壳、需要各种其他零部件,我们都能给你拼起来,包括各种散热系统。剩下的,你只管去定义就行了。我们的工业4.0准备好了,欢迎来下订单。

所以,大家也可以想一想,中国机器人到底有没有遥遥领先?真正领先的,到底是什么?我们真正领先的是整个机器人产业链,尤其是所有零部件的产业链。


背景图片

昨天 — 2026年4月20日未分类

超越 OpenClaw ? 融合三大 AI Agent 的 ZeusHammer 给我带来的终极震撼

作者 伯衡君
2026年4月20日 21:27
说出来你可能不信,伯衡君这两天的一个新发现,伯衡君愣是凌晨三点才睡着。事情是这样的。伯衡君平时就喜欢鼓捣各种AI Agent,从OpenClaw到Claude Code,从Hermes到各种开源项目,基本上市面上叫得出名字的,伯衡君都玩过一遍了。然后呢,前两天在GitHub上瞎逛的时候,偶然看到了一个叫ZeusHammer的项目。伯衡君一开始还想,这名字真中二,不就是一个游戏里的武器名称吗。结果呢,点进去一看,好家伙,直接给伯衡君干清醒了。这个项目,创始团队干了一件伯衡君觉得在开源社区里史无前例的事情——他们把三个顶级开源AI Agent项目的核心模块,全部融合在了一起。哪三个?OpenClaw、Claude Code、还有Hermes……

SpaceX IPO估值争议全解析

2026年4月20日 21:10
夜色中的发射场上,一枚巨大的可回收火箭立在探照灯与薄雾中,远处电子屏闪烁着IPO与估值数字,前景是举着招股书和望远镜的人群,宏大而紧张的开场氛围,羊皮纸,钢笔彩色手绘的统一风格。

大家好,欢迎收听老范讲故事的 YouTube 频道。

今天咱们来讲一讲:SpaceX IPO 在即,史上最大的 IPO 可能就要来了,我们到底要不要冲?

现在外界传出的计划是,SpaceX 可能在 6 月份上市,按照 1.75 万亿到 2 万亿美元的市值去上市。你跟还是不跟?这不是一家火箭公司上市,而很可能是人类商业史上最大的 IPO,应该没有之一。

像沙特阿美那种,严格来说不太能算,因为它是在内部股市上市,而且很多账目并没有那么透明和公开。但 SpaceX 如果是在美国上市,那就不一样了。

SpaceX 的估值:财务支撑,还是“市梦率”?

 一张古典书桌上摊开两本对照账本,一边写着利润、收入、现金流,另一边漂浮着火星城市、卫星网络和星舰蓝图,天平在理性数字与未来幻想之间摇摆,羊皮纸,钢笔彩色手绘的统一风格。

那么问题来了:SpaceX 到底是一个有财务数据支撑的市值,还是一个梦想支撑的“市梦率”?

“市梦率”这个词,是我原来跟币圈的人打交道时学到的。正常投资圈里讲的是市盈率、市销率。

市盈率就是市值和利润的比例,比如 30 倍市盈率,就是利润 1 亿,市值 30 亿。市销率是很多亏损公司会用的,因为利润是 0 或者负的,算不出市盈率,那就看销售额和市值之间的比例。

而“市梦率”的意思就是:没有任何传统财务道理,就是梦想,它就值这么多钱。

我当时为什么学了这么个词?那时候他们都做 NFT,非同质化代币。我就问他们,为什么不在后头绑定一个实体?他们说,绑定了实体以后,就锁定价值了。比如你买了什么代币以后,我给你一瓶酒,那这就锁定价值了,这不行。必须得是一个虚无缥缈的东西,才能有市梦率,大家才愿意玩。

那么,SpaceX 大家玩的是不是市梦率?这就是咱们今天要讨论的。

所以这一次真正考验的,不是知识点,是信仰。考验信仰的时刻到了。还是借用币圈这句话。

今天这一场,我们不只是聊 SpaceX 值不值这个钱,我们要聊的是,华尔街到底是在给一家公司定价,还是在给人类未来 20 年的想象力定价。

先看事实:SpaceX 上市流程到底走到哪一步了?

一间安静的法律事务办公室里,厚厚的上市申请文件、SEC印章、红笔批注和保密封套整齐摆放,窗外隐约可见曼哈顿天际线,画面严谨而克制,羊皮纸,钢笔彩色手绘的统一风格。

首先,咱们先捋一下事实,到底有哪些事情是确实发生了。

先说关键判断:SpaceX 确实计划在今年上市,目前大家猜测是 6 月份。因为老马同学好像 6 月份过生日,他这个人一般还是喜欢在自己生日或者一些特殊纪念日的时候,干一些惊天动地的事情。

最重要的动作,不是敲钟,而是国内很多报道里说的“保密递表”。这个说法挺有意思,就是向 SEC,也就是美国证券交易委员会,递交上市申请书,而且是保密递交。

但这个说法和英文原文报道不完全一样。英文报道的意思就是递交材料去了,而在中文环境里,很多人会特别强调“保密”这件事,好像马斯克做事心虚一样。

其实没什么。因为在美国,企业想上市,最开始递交材料这个过程,本来就是不公开的,制度规定就是这样,不是什么偷偷摸摸干坏事。

美国 IPO 的正常流程是什么?

你先要递交一个文件,里面写清楚我要上市,我的财务报表什么样,挣了多少钱,有多少债务,借了多少钱,未来计划是什么,所有东西都要写清楚。

这个文件有两个特别关键的要求:

  1. 格式完整,就是 SEC 要求的所有项目你必须写全;
  2. 内容真实,不能瞎编。

美国股市是注册制。注册制下,提交的文件只要求格式完整、内容真实,不要求你必须赚了多少钱,连续几年盈利,销售额达到多少以上。它不审这些,只审前面两项。

只要你该给的都给了,内容都是真的,我就让你上市。至于上市以后有没有人买你的股票,这股票到底值多少钱,跟 SEC 没关系。甚至你上市后过两天跌到 1 块钱以下,连续多少天退市,也跟它没关系。这就是注册制。

所以,第一批递上去的文件确实是不公开的。然后 SEC 会给反馈意见,说你这缺点东西,那个地方写得不够完整。它主要做形式审查,不做价值判断。

像 SpaceX 这么大的明星企业,可能还是会看得很认真,整个过程会反复好多次。最后确认没问题了,招股说明书才会正式公开。

公开之后,接下来会发生什么?

一支路演团队拖着行李箱穿梭于纽约、波士顿和旧金山,高楼会议室里机构投资者围坐长桌,屏幕上展示火箭发射、卫星用户增长与报价区间,羊皮纸,钢笔彩色手绘的统一风格。

公开以后干什么?下一件事叫路演

路演不是字面意义上在路边搭个棚说“来买我股票”,不是。路演是 CEO、承销团队、CFO 以及一些关键人员,一起去找家族基金、学校基金、大机构,跟他们聊。不是说你一上来就公开募股,而是先去找这些核心投资者沟通。

像当时猎豹上市的时候也是这样。小一点的机构发请柬,大一点的你得亲自去,不管在美国哪个角落,都得坐到人家面前,单独聊一次:我们公司是干嘛的,我们为什么挣钱,我们为什么值钱。这就叫路演。

路演完了以后是计价。上市到底值多少钱,不是拍脑袋来的。路演之后,很多人当场就会说,我愿意买,我愿意买多少,我愿意用什么价格买,现场就开始商量了。然后大家把这些意向记下来,最后形成一个报价区间。不是一个死数,而是一个范围。

这个时候,像马斯克这样的人,或者 CEO,还可以在报价区间里做选择。选低一点比较保险,选高一点上市时会更好看一些。这个过程就叫定价。

定价以后,才是真正的 IPO,也就是第一次挂牌公开销售。到了纽交所、纳斯达克,把牌挂上,大家就可以公开买卖股票了。

所以,所谓“秘密递表”,其实就是正常流程。不要觉得马斯克有什么见不得人的事情。

为什么是现在上市?

那么,为什么要在这个时候上市?这个问题很重要。

SpaceX 折腾这么多年了,火箭也发了,Starlink 也做了,马斯克本人也不像穷人,自己身家都有 8000 亿美元了,那为什么一定要在这个时候上市?

原因一:现金流正处于最好看的时候

第一个原因很简单:上市要挑现金流最好看的时候。这就跟什么时候结婚一样,当然是长得最漂亮、身体最好的时候去。

现在 Starlink 的现金流非常好看,一年能挣一百几十亿美元,而且利润还很高。2025 年的 SpaceX,像印钞机一样挣钱。在这个时候不上市,还等什么时候?

如果你 2024 年上市,报表没法看。虽然美国是注册制,但你报表太难看,路演的时候就没人愿意报价,上市以后没人交易,搞个退市,马斯克这个世界首富也丢不起这人。

原因二:Starship 正处在爆发前夜

还有别的原因。比如 Starship 处在爆发前夜。不要把所有好消息都放完了以后再上市,而是要在“马上要爆发”的时候上。

SpaceX 的 Starship,下一期 V3 版据说 5 月份要首飞,这又是一个利好。

原因三:市场窗口与竞争压力同时出现

清晨的资本市场交易大厅与海边发射基地形成并置画面,一边是跳动上升的纳斯达克曲线,另一边是等待点火的星舰与逼近的竞争者火箭,空气里充满窗口期的紧迫感,羊皮纸,钢笔彩色手绘的统一风格。

再比如 xAI 这笔巨大的并购。有些人觉得是好事,也有人觉得有问题,这个待会单独讲。

还有一个背景是,2026 年可能是美国 IPO 市场修复的一年。现在纳斯达克前两天好像已经 11 连涨了,今天能不能继续涨不知道。上市一定要选市场机会好的时候,市场天天阴跌、没人交易的时候,你不可能去上市。

还有一个非常重要的原因:竞争对手快追上来了

现在能做轨道级发射,还能把火箭回收回来的公司,只有 SpaceX 一家。马上就要有第二家了。现在全球能提供低轨通信卫星服务的,Starlink 基本也只有它一个,但第二个也快来了。

所以,现金流最好看,Starship 马上又要有进展,IPO 市场又热,竞争对手还没真正追上来,这个时间点就是最好的。再不上,就晚了;再早上,又亏了。

先摆数字:SpaceX 现在到底值多少钱?

这是事实层面。下面咱们摆数字。

先摆最炸裂的数字。现在传出来的估值是 1.75 万亿到 2 万亿美元,非常高。融资规模有的说 500 亿,有的说 750 亿,还有看到写 850 亿美元的。

所谓融资规模,就是 IPO 时要向市场卖出去的那批股票,计划募集多少钱。如果真成了,这几乎就是人类商业史上最夸张的 IPO 之一。

很多人就吵起来了,说这公司疯了吧,它能值这么多钱吗?咱们拆开财务数据一看,SpaceX 就像两家公司,甚至像两个完全不同的世界糅合在一起。

EBITDA 很好看,净利润却可能很难看

一块巨大的黑板上写满EBITDA、净利润、折旧、摊销等公式,前景是火箭、卫星和发射塔的剪影投下沉重阴影,亮眼的营业数据与暗色亏损数字形成强烈对比,羊皮纸,钢笔彩色手绘的统一风格。

首先,2026 年 1 月份路透社报道,说 SpaceX 的 EBITDA 大概是 80 亿美元,非常挣钱。Starlink 利润很高,可能能到一半左右,收入也很高,大概有 120 亿到 130 亿美元,甚至更高。

但另一套说法是,2026 年 4 月份,路透社引用 The Information 的消息,说 2025 年 SpaceX 净亏损 50 亿美元。

这到底怎么算的?

先讲一下 EBITDA 是什么。最简单的利润概念叫毛利:10 块钱买的东西,20 块卖了,这中间的差就是毛利。再往下扣掉宣传推广费用、销售费用、部分人员工资之类,形成 EBIT。EBITDA 再进一步,是不算利息、不算税、不算折旧、不算摊销的利润口径。净利润则是把这些全算进去后的结果。

所以,SpaceX 现在的状态是:毛利肯定很高,EBITDA 也是正的,而且还挣了 80 亿美元;但是把利息、税、折旧、摊销都算上以后,净利润可能就是亏 50 亿美元。

这其实不难理解。SpaceX 是一家非常重资产的公司,火箭、卫星、发射场、实验场,资产都太重了。所以从 EBITDA 到净利润之间,差异会非常大。

如果你是多头,相信它未来一定会涨,那你就会强调:它 EBITDA 是正的,去年挣了 80 亿美元。如果你是空头,就会说:这帮人讲笑话,你看看这些机器折旧有多快,卫星发上去过几年就报废,还得再补发,所以你得看净利,净利是亏 50 亿美元。

同样一家公司,同一本账,这两个数都对,没有哪个是错的,只是看问题的角度不同。

而且这里头还有一个关键点:市场普遍认为,净亏损这个口径里可能包含了 xAI 的影响。那是不是原来挣 80 亿,xAI 亏了很多钱,最后变成亏 50 亿?其实也不是。xAI 2025 年亏损大概十几亿美元,靠它自己搞不出这么大的变化。相对 SpaceX 的体量来说,它影响很小。

真正的现金奶牛,其实是 Starlink

第二个数据:Starlink 才是今天真正的现金奶牛

从收入结构上看,整个 SpaceX 的估值其实不是靠火箭挣钱。原来很多人从工程师视角理解,觉得它靠军方订单、政府单、NASA、阿尔忒弥斯项目这些挣钱。其实这些钱没有大家想的那么多。

SpaceX 整体收入大概在 150 亿到 185 亿美元之间,其中 Starlink 占 110 亿到 123 亿美元。也就是说,一大半,甚至可能接近三分之二的收入,都来自 Starlink。就是大家买它的锅,然后每个月交会员费,这才是 SpaceX 最赚钱的部分。

这里有一个很重要的点。如果你只是发火箭的公司,收入会有几个特点:

  • 非常不规律,收入波动极大;
  • 风险很高,发射失败就可能造成严重影响;
  • 客户很单一。

做投资的时候,一旦说你的客户单一,这基本就是个贬义词。因为风险很大。你的客户今天跟你做,明天倒了、跑了、换供应商了,你怎么办?

传统火箭公司的客户通常都非常单一,比如 NASA。毕竟谁能自己掏钱发火箭?

但 Starlink 不一样。它是稳定现金流,每个月都有人交钱。所以一旦你从火箭公司变成带有大规模订阅收入的卫星通信公司,整个估值逻辑就变了。

所以,SpaceX 不能单纯按火箭公司来估值。它更像是一个火箭 + 卫星 + 通信 + AI + 政府合同 + 部分军工能力的组合体。

按传统估值模型看,2 万亿美元到底行不行?

一排旧世界的量具和尺子摆在桌上,分别标着军工、电信、数据中心、成长航天,它们试图丈量一枚冲出边框的巨型火箭,尺度明显失真,寓意传统模型失效,羊皮纸,钢笔彩色手绘的统一风格。

接下来,咱们就要看,2 万亿美元这个市值到底行不行。

因为刚才讲了,它是亏损公司。亏损公司没有 PE,只能看 PS,也就是市销率,用收入和市值之间的比例来算。

最简单的估值方法叫同类公司比较法。找一堆跟你差不多的公司,看看它们收入多少、估值多少,然后按比例估。

如果按火箭和军工公司估值

先看火箭和军工公司。波音的 PS 大概 2 倍;洛克希德·马丁大概 1.9 倍;L3Harris 大概 3.1 倍。

按这个逻辑,SpaceX 去年收入 150 亿到 185 亿美元,乘上最高的 3.1 倍,市值也就 500 多亿美元,离 2 万亿美元差得实在太远。

如果按电信运营商估值

那不能按老牌工业航天公司算。咱按电信公司算吧,毕竟有 Starlink。

ATT 的 PS 是 1.47 倍,Verizon 是 1.43 倍,T-Mobile 是 2.41 倍。按这个算,更不值钱了,也就 400 多亿美元。

如果按基础设施和 AI 基建公司估值

那怎么办?再按基础设施、数据中心、AI 基建公司来算。因为马斯克说过,准备把算力中心送上太空。

那有些公司,比如 Vertiv,PS 是 11 倍;Equinix 是 7.45 倍。按这个逻辑给 SpaceX 估值,大概能到 900 亿到 2000 亿美元之间。

还是不对。就算给到这么高,也离 1.75 万亿到 2 万亿美元差得很远。

如果按极端成长型航天公司估值

再极端一点,有一个案例叫 Rocket Lab,它的 PS 是 78 倍。如果按 70 到 80 倍这种极端成长航天公司的倍数给 SpaceX 估值,那它的估值可以到 1.05 万亿到 1.48 万亿美元之间。

但即使这样,距离 1.75 万亿到 2 万亿美元,还是差着一大截。

这就是为什么我今天要讲“市梦率”,要说考验信仰的时刻到了。我们到底信不信马斯克,信不信星辰大海这套叙事。

现在用 SpaceX 的收入,乘上各种各样的 PS,基本都没办法把 2 万亿美元的估值真正撑起来。

最后只能算什么?Starlink 成长很快、现金流很高;Starship 马上可能成功;Starship 一旦成功,整个成本结构会发生革命性变化;别人又追不上;xAI 蓄势待发,因为原来它只是火箭公司,一旦把 xAI 挂进来,你就可以把它讲成 AI 基础设施公司;再加上马斯克本人的品牌溢价;背后还有世界首富;再加上全球稀缺资产溢价——唯一能把重型火箭送入轨道还能回收,唯一能提供全球低轨通信卫星服务。

只有把这些全加在一起,才有可能算出这样一个数。

所以,如果按火箭公司估,SpaceX 只值几百亿美元;按电信运营商估,也只值几百亿;按高成长基础设施平台估,大概到 2000 亿;按 Rocket Lab 这种高成长航天公司估,大概能到 1 万亿。至于 1.75 万亿到 2 万亿美元,中间这段缺口,大家就只能靠信仰来撑了。

围绕 SpaceX 上市的四大核心冲突

在这样的故事里,有很多冲突。

冲突一:财务数据冲突

刚才讲了,EBITDA 很漂亮,但净亏损也非常难看。接下来要看什么?

  • 要看未来几年,它能不能把原来计划 5 年报废的卫星多用一段时间;
  • 要看猎鹰火箭这种高频复用能不能持续把折旧数据摊薄;
  • 要看 Starship 的二级火箭什么时候真正可回收;
  • 要看更新换代速度是不是太快、补星成本是不是太高。

所以,它有可能是一个超级泡沫,也有可能是一个历史级神话。这部分冲突非常激烈。

甚至还有人说,SpaceX 这公司其实只有 Starlink 挣钱,别的都不挣钱。严格说,这话基本也没错,因为刚才咱们看数据了,Starlink 贡献的收入占比极高。

冲突二:它会不会成为“史上最大的抽血机”?

当时猎豹去美国上市的时候,我们特别怕一件事,就是一定要在阿里之前上市。为什么?怕它把市场上的血吸干了。资本市场流动性是有限的,一个超级大 IPO 上来,把钱都吸走,后面的公司就没人买了。

所以现在也有两种观点:

  • 一种说,SpaceX 一上市,所有航天相关股票都会涨,因为行业老大来了,大家对航天赛道会更感兴趣;
  • 另一种说法正相反:SpaceX 一旦把 750 亿到 850 亿美元的现金从市场里抽走,整个行业都会非常低迷,因为钱都被吸走了。

比如你现在想买 SpaceX,钱从哪来?很可能就是卖掉原来持有的其他航天股。那后面再有航天公司来上市,市场就会拿它和 SpaceX 比。一比之下,啥也不是,那你还怎么募资?

所以,到底会带来市场繁荣,还是会造成行业抽血,这是第二个冲突。

冲突三:xAI 到底是加分项还是减分项?

机械火箭骨架与发光神经网络在半空中拼接,一侧是兴奋的投资人描绘天地一体化平台,另一侧是皱眉审账的股东盯着烧钱曲线,形成鲜明对立,羊皮纸,钢笔彩色手绘的统一风格。

乐观派说,一旦把 xAI 加进来,故事就完全升级了。从航天公司,变成天地一体化平台:卫星网络 + 火箭 + AI,直接升级成太空基础设施加 AI 平台。

没有 xAI 的时候,你只是火箭加通信运营商;有了 xAI,你就可以讲更大的故事,估值体系自然变化。

这在资本市场里很常见。比如一个低估值的传统水泥厂去买游戏公司,背后的逻辑也是估值体系切换。传统行业估值低,科技行业估值高,一旦买进来,整个讲法就变了。

但悲观派会说,你本来卖的是 SpaceX,结果顺手打包了一台还在疯狂烧钱的 AI 发动机,这事不行。尤其马斯克自己都说过,xAI 一开始搞错了,现在还在重建。

那对于股东来说,就会担心:你是不是把自己前面搞砸的资产,一层一层往上市主体里装?先是 Twitter 改名成 X,后来 X 被 xAI 收了,现在 xAI 又想往 SpaceX 里装。这是不是在接烂账?

而且 xAI 在和 OpenAI、Anthropic、谷歌的竞争里,目前也没明显看出胜算。这就让很多人对这个交易本身持怀疑态度。

所以,梦想派会觉得 xAI 是巨大加分项,财务派则会觉得它是明显减分项。

冲突四:竞争对手会不会削弱 SpaceX 的稀缺性?

第四个冲突,是贝索斯的新格伦火箭和新卫星,会不会给 SpaceX 带来新的估值压力。

贝索斯一定要在这个时间点发这些东西,肯定是有原因的。蓝色起源原来在低轨火箭回收这块已经有进展,而现在,能够把火箭送到更高轨道还能做回收,这件事原来基本只有 SpaceX 能干。现在蓝色起源也开始做了。

上一次发射的时候,它实际上已经把火箭回收回来了。按正常逻辑,回收回来以后,你得多回收几次才能证明复用能力。但礼拜天它就要复用这颗火箭,再飞一次。

而且火箭上还写了一句话:“别跟我讲概率。”这句话来自《星球大战》,是 Han Solo 说的。意思也很明白:别老拿概率吓我,干就完了。

如果它这次成功了,对 SpaceX 的稀缺性肯定是有打击的。原来只有你一个,现在有第二个选择了。

另外,它发的新卫星,也是类似 Starlink 的功能,而且可以直接连手机,不需要地面锅。Starlink 你还得买个锅,锅连卫星,再把信号转成 Wi-Fi 给你用。贝索斯这套如果真能直接连手机,那对 Starlink 的估值也会形成一定压力。

旧世界的尺子,量不准新世界的公司

一位制图师在旧式工作台前举起木尺测量天空中的未来城市,月球基地、轨道算力中心、火星移民飞船从云层后延展出来,旧尺明显短小,画面充满时代错位感,羊皮纸,钢笔彩色手绘的统一风格。

以上这些,就是围绕 SpaceX 上市这件事,现有的事实、数据和冲突。

下面,咱们讲梦想的部分。

前面讲的所有数据、PS、上市流程、上市规定、各种冲突,其实都还是旧世界的估值方法。都是用传统方式去看 SpaceX 这样的公司。

但你不能只用传统方法看它,因为它要做很多新的东西。它一旦成功,可能整个人类社会都会变得不一样。大家现在拿工业公司、军工公司、火箭公司、运营商、数据中心这些老尺子去给 SpaceX 估值,这件事本身就有问题。

SpaceX 最贵的部分,恰恰不是已经写在报表里的东西,而是未来 20 年的想象空间。

马斯克要的不是火箭发射,也不是 Starlink 宽带这么简单。他想把算力送上太空,做太空算力中心;他想建月球城市;阿尔忒弥斯项目往后推进,未来在月球上建立固定定居点不是不能想;再往后,他还要搞火星殖民。

这些东西,今天根本没法被计算进财务模型里,也没法用会计语言完整描述出来。

所以,前面所有挑刺的人,其实用的都是昨天的尺子,在量明天的东西。你看传统公司,可以这么看;但马斯克的 SpaceX,如果完全用这种方式去衡量,本身就是有问题的。

当然,也不能说他们全错。财务和估值依然非常重要。有时候真就是差着一口气,马斯克就挂了;续上这口气,他就过去了。挂掉了,你可以说他是庞氏骗局,是骗子;挺过去了,他就是全人类的英雄。中间真的是一线之隔。

而且,SpaceX 最具未来性的部分,今天确实没法体现在报表里。我们以前做投资的时候,最不认真看的一部分,往往就是未来三年的营业计划。别说未来三年,未来三个月你真能搞明白吗?很多时候也搞不明白。

但我们还是会要求创始人写,因为我们要看他的规划能力和合规意识,而不是因为里面的数据真有多大参考价值。

所以,按旧逻辑看,SpaceX 真的是太贵了,贵得没边;按新逻辑看,这个价格也许仅仅是一个开始。

如果 SpaceX 真的能成功上市,我估计它大概率还是能成功的。如果市场真的愿意为这个梦想买单,那现在的估值也许不是终点,只是一个开盘价。

所以你会发现,所有关于 SpaceX 的争论,最后都会走向一个很玄妙、又很现实的问题:你相不相信人类文明会被这种公司往前推一步。

最后的问题:到底冲,还是不冲?

最后还是那句话,考验信仰。

“褒贬是买主,喝彩是闲人。”

这句话什么意思?不要觉得那些认真挑 SpaceX 毛病的人就一定是保守、落后、不懂未来。未必。很多认真挑毛病的人,反而是真正要为手里的钱负责的人。

你上来说“SpaceX 太棒了,最厉害了,我们一定要支持”,很多时候这些人未必真的下单,可能只是旁边吃瓜的。

我们以前做投资也是这样。有些项目我心里明明知道特别喜欢,觉得未来很有机会,团队也特别好,我会怎么干?我会挑毛病。我会说你这有问题,那有风险,你估值便宜一点,让我进去。为什么?因为我真想买。

如果我觉得这个项目一点机会都没有,我反而会说,挺好挺好,谁爱投谁投,反正我不投。

所以,前面那些认真挑毛病的人,可能才是真正想买货的人。嫌货才是买货人。有些人认真挑毛病,未必是真的不喜欢它,可能只是想等一个更好的价格,再狠狠干一把。

最后的问题,不是 SpaceX 的财务完不完美,而是你信不信这个未来。

结论

黄昏下的投资者站在交易所台阶与发射塔之间,手里一边是写满风险的笔记,一边是通往星海的船票,远方火箭点火升空,象征在财务理性与乐观信仰间做最终抉择,羊皮纸,钢笔彩色手绘的统一风格。

我的结论先放这:SpaceX 今年大概率会上市。Anthropic 今年应该也有机会上市,问题不大。OpenAI 到底能不能上去,不太好说。

SpaceX 现在的财务数据,确实还完全撑不起当前传出的估值。那到底应不应该跟?我个人的看法是,这已经不是单纯的财务题了,而是我们是不是要为未来的想象力去下赌注。

马斯克这些年来反复强调一句话。2023 年接受播客采访时说过,2025 年 9 月 18 日在 X 上发帖也说过,2026 年 1 月 22 日在达沃斯闭门发言里也表达过类似意思。核心是:宁可怀着乐观之心、哪怕判断失误地生活,也胜过秉持悲观态度、即便始终正确地度日。

意思就是,我宁肯乐观,哪怕我判错了,也比一直悲观、一直正确要好。

面对 SpaceX 这样一个无法用旧模式描述的新生事物,我们还是应该尽量保持乐观之心,不要盲目跟着那些挑毛病的人一起起哄。

所以这件事情,最后不是财务题,而是价值观测试、想象力测试,以及信仰测试

考验信仰的时刻到了。大家到底冲,还是不冲?这就是咱们今天的故事。

阿里云 Coding Plan Bug:流式输出内容截断

2026年4月20日 08:00

问题背景

本文记录在使用 阿里云百炼平台 Coding Plan(GLM-5 模型) 时遇到的另一个严重 bug:流式输出内容截断。 上一篇博文记录了 Tool Call arguments 输出协议异常的问题,这一次遇到了更基础的问题:普通文本回复的内容被截断,直接丢失了最后的几个字符。 阿里云售后人员的回复是:”这是工具问题”。好吧,那就用这篇博文来证明,这根本不是工具问题。

问题现象

请求场景

在 chat.nvim 中使用 GLM-5 模型,发送最简单的问候消息 “你好”。这次请求完全没有触发 Tool Call,就是一次最普通的聊天对话。

完整原始日志(铁证)

以下是 curl stdout 的完整原始日志,逐行打印,未做任何删减:
data: {"choices":[{"delta":{"content":null,"reasoning_content":"用户","role":"assistant"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"说"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"\"你好"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"\","},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"这是一个简单的"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"问候。"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"根据我的"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"性格设定"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":",我应该"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":":\n1"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"."},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":" 热"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"情"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"友好\n"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"2."},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":" 简"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"洁直接"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"\n3"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"."},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":" 可能使用"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"一些表情"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"符号\n\n"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"我应该回忆"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"一下是否"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"有什么相关的"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"记忆,"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"比如用户的"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"偏好等"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"。不过"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"这是一个新的"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"对话开始"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":",我先"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"友好地"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"回应,"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"看看用户"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"需要什么"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"帮助。\n\n"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"我应该:\n"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"-"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":" 友好"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"地问候"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"\n-"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":" 简单"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"介绍自己"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"可以"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"做什么\n"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"- "},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"询问需要"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"什么帮助"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"\n\n"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"不需要调用"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"任何工具"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":",只是"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"简单的对话"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":null,"reasoning_content":"问候。"},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"你好!","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"我是 No","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"va,","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"来自 N","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"eovim 的小","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"星星 :","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":")","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"\n\n","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"我可以","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"帮","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"你:\n- 📝","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":" 编","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"写和修","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"改 ","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"Lu","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"a 插","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"件代码","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"\n- 🔍","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":" 搜","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"索、","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"阅读项目文件\n","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"- 📦","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":" Gi","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"t 操","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"作(提","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"交、","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"分支","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"管","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"理等)\n","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"- 💾","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":" 记","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"住你","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"的偏好和习","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"惯","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"\n- 🌐","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":" 搜索网","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"络、获","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"取网页内","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"容\n\n","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"有什么","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"delta":{"content":"我","reasoning_content":null},"finish_reason":null,"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[{"finish_reason":"stop","delta":{"content":"","reasoning_content":null},"index":0,"logprobs":null}],"object":"chat.completion.chunk","usage":null,"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: {"choices":[],"object":"chat.completion.chunk","usage":{"prompt_tokens":16418,"completion_tokens":191,"total_tokens":16609,"completion_tokens_details":{"reasoning_tokens":106},"prompt_tokens_details":{"cached_tokens":16414}},"created":1776597535,"system_fingerprint":null,"model":"glm-5","id":"chatcmpl-f87aa751-9260-9884-8cb6-f33c540cab94"}

data: [DONE]

问题定位

从完整日志中可以清楚看到:
  1. 前面的 reasoning_content(思考过程)输出正常,从 “用户” 到 “问候。” 共 55 行
  2. 后面的 content(正式回复)开始输出,从 “你好!” 到功能介绍
  3. 关键截断点:最后一个有 content 的 chunk 是 {"content":"我"}
  4. 紧接着就是一个 finish_reason: "stop" 的 chunk,content 是空字符串
  5. 然后是 usage 统计和 [DONE]
按照正常的回复逻辑,AI 应该说 “有什么我可以帮你的吗?”,但实际只输出了 “有什么我”,后面的 7 个字符”可以帮你的吗?”直接丢失了

这不是工具问题

请求原理

chat.nvim 的请求逻辑非常简单:
function M.request(opt)
  local cmd = {
    'curl',
    '-s',
    'https://coding.dashscope.aliyuncs.com/v1/chat/completions',
    '-H', 'Content-Type: application/json',
    '-H', 'Authorization: Bearer ' .. api_key,
    '-X', 'POST',
    '-d', '@-',  -- 从 stdin 读取请求体
  }
  
  -- 启动 job,通过 stdin 发送请求体
  local jobid = job.start(cmd, {
    on_stdout = opt.on_stdout,  -- 处理 stdout 输出
    on_stderr = opt.on_stderr,
    on_exit = opt.on_exit,
  })
  job.send(jobid, body)
end

解析逻辑

SSE 解析逻辑也只是简单地:
  1. 检测 data: 前缀
  2. 解析 JSON
  3. 提取 content 字段
if choice.delta.content and #choice.delta.content > 0 then
  sessions.on_progress(id, choice.delta.content)
end

为什么这不是工具问题

论点 证据
curl 只是传输层 curl 不做任何内容处理,只是把 stdout 原样输出
日志是完整原始输出 日志是逐行打印的,不存在截断或遗漏
SSE 格式正常 每个 chunk 都是合法的 data: {...} 格式
finish_reason 正确返回 服务端明确返回了 finish_reason: "stop"
usage 统计显示完成 completion_tokens: 191 表明模型认为已完成
结论:服务端已经认为回复完成了(finish_reason: "stop"),但实际输出不完整。这是服务端生成逻辑的 bug,客户端无法检测或修复。

可能的根因分析

推测的原因

根据输出特征,推测可能的原因:
  1. 提前终止生成:模型在生成过程中被服务端提前终止,但没有返回错误信息
  2. 缓冲区问题:最后几个 token 在缓冲区中被丢弃,未发送到客户端
  3. token 计数错误:服务端在某个阈值(如 completion_tokens 达到某个值)时错误地终止生成
  4. 流式输出同步问题:模型生成结束和服务端发送 finish_reason: stop 的时机不同步

为什么 usage 显示 191 tokens

有趣的是,usage 显示 completion_tokens: 191,其中 reasoning_tokens: 106。这意味着:
  • reasoning_content(思考):约 106 tokens
  • content(回复):约 85 tokens(191 - 106)
但实际输出的 content 远少于 85 tokens(只有几个短句),说明:
  • 要么:token 计数有误
  • 要么:有大量 content 在服务端被丢弃

与 Tool Call Bug 的关联

上一篇博文记录的 Tool Call bug 是 arguments 输出协议异常。这次遇到的是更基础的 content 截断问题。 两个问题的共同特征:
特征 Tool Call Bug Content 截断 Bug
问题层级 协议层 内容生成层
表现形式 数据格式异常 数据丢失
影响范围 Tool Call 功能 所有回复
客户端可检测 ✅ 可以检测并容错 ❌ 无法检测
Content 截断 bug 更加隐蔽,因为:
  1. 格式合法:每个 SSE chunk 都是合法的 JSON,不会触发解析错误
  2. finish_reason 正常:服务端明确返回了 stop,客户端会认为正常完成
  3. 无错误信息:没有任何错误提示,客户端无法知道内容不完整

对用户的影响

实际影响

影响项 严重程度
回复不完整
功能介绍缺失
用户体验下降
无法自动检测

用户感知

用户发送消息后,收到的是不完整的回复。例如:
  • 期望:”有什么我可以帮你的吗?”
  • 实际:”有什么我”
用户可能会:
  1. 认为模型回答奇怪
  2. 再次发送消息询问
  3. 怀疑客户端有问题

无法修复

客户端无法修复这个问题,因为:
  1. 没有任何错误信息
  2. finish_reason: "stop" 表示正常结束
  3. 客户端无法知道应该还有后续内容
唯一的”修复”方式是:用户自己发现回复不完整,手动再次询问。

给阿里云的建议

问题定性

这是一个 服务端内容生成层 bug,具体表现为:
  1. 流式输出在模型生成过程中提前终止
  2. 最后的 token 被丢弃,未发送到客户端
  3. 服务端错误地认为生成已完成,返回 finish_reason: "stop"

希望的修复

  1. 确保模型生成的所有 token 都正确发送到客户端
  2. finish_reason: "stop" 之前,确保所有 content 已完整输出
  3. 检查 token 计数逻辑,确保与实际输出一致

测试建议

建议阿里云增加以下测试:
# 简单测试:检查输出是否完整
curl -N https://coding.dashscope.aliyuncs.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_KEY" \
  -d '{"model":"glm-5","messages":[{"role":"user","content":"你好"}],"stream":true}' \
  > output.txt

# 分析:最后一个 content chunk 后是否直接出现 finish_reason: stop
grep "content" output.txt | tail -5
grep "finish_reason" output.txt

结论

又一次踩坑阿里云 Coding Plan。上一篇是 Tool Call 协议异常,这次是 Content 截断。 阿里云售后说”这是工具问题”,但本文已经清楚证明:
  1. curl 只是传输层,不做内容处理
  2. 日志是完整原始输出
  3. 服务端明确返回了 finish_reason: "stop"
  4. 客户端无法检测或修复
这是服务端 bug,不是工具问题。 建议使用阿里云 Coding Plan 的开发者:
  1. 注意检查回复内容是否完整
  2. 发现截断时手动再次询问
  3. 向阿里云提交工单,附上原始日志

参考资料

Python Mock 第三方依赖的四种策略

2026年4月20日 08:00
Sophie Koonin 在 localghost.dev 上写了一篇文章,以她的 Choirbot 项目(一个管理合唱团排练的 Slack bot)为例,展示了在 Jest 中 mock 四种不同类型的第三方依赖的方法。本文借鉴了她的思路,但把所有示例改写为 Python 版本——用 =unittest.mock= 和 =responses= 等 Python 工具实现同样的四种策略。每种策略针对不同的依赖接口特点:客户端类、工厂模式、数据库 ORM、HTTP 请求,从简单到复杂逐步展开。 ** 前置知识: =unittest.mock= 是什么 =unittest.mock= 是 Python 标准库中专门用于测试时"替换真实对象"的模块。它的核心思路很简单:测试时你不想真的去调用 Slack API 或连接数据库,那就用一个"假的"对象来代替——这个假对象长得很像真的,但它不会产生任何副作用,你可以随意控制它的返回值、检查它被怎么调用了。 本文用到的三个核心工具: - =MagicMock= :万能替身。你访问它的任何属性、调用它的任何方法都不会报错,默认返回另一个 =MagicMock= 。你可以通过 =.return_value= 设置方法的返回值,通过 =.assert_called_once_with()= 验证方法是否被正确调用。 - =patch= :偷梁换柱器。它能在测试运行期间把代码中的某个类或函数替换成 =MagicMock= ,测试结束后自动恢复原样。用法通常是 =@patch('模块路径.对象名')= 装饰器。 - =spec= 参数:给 =MagicMock= 加上约束。 =MagicMock(spec=SomeClass)= 创建的 mock 只能访问 =SomeClass= 中真实存在的方法,防止你拼错方法名却不报错。 * 策略一: =patch + MagicMock= ——适用于客户端类 如果你已经把第三方 SDK 封装成了自己的客户端类,最高效的 mock 方式不是去 mock 整个 SDK,而是 mock 你自己的客户端。Python 的 =unittest.mock.patch= 可以在测试运行时自动替换目标对象。 #+begin_src python # src/slack_client.py class SlackClient: def __init__(self, token): self.token = token def post_message(self, channel, text): # 调用真正的 Slack API ... def get_reactions(self, channel, timestamp): # 调用真正的 Slack API ... #+end_src 假设被测代码中有一个函数 =detect_raised_hand= ,它内部会实例化 =SlackClient= 并调用其方法: #+begin_src python # src/detector.py from src.slack_client import SlackClient def detect_raised_hand(channel, timestamp, token): client = SlackClient(token) reactions = client.get_reactions(channel, timestamp) return any(r['name'] == 'raised_hands' for r in reactions['message']['reactions']) #+end_src 在测试中用 =patch= 替换 =SlackClient= 类,被测代码内部的 =SlackClient(...)= 会自动返回 mock 实例: #+begin_src python # tests/test_detector.py from unittest.mock import patch from src.detector import detect_raised_hand @patch('src.detector.SlackClient') def test_detect_raised_hand(MockClient): # MockClient 是替换后的 MagicMock 类 # MockClient.return_value 是"实例化"时返回的 mock 实例 client = MockClient.return_value client.get_reactions.return_value = { 'ok': True, 'message': { 'reactions': [{'users': ['U123456'], 'name': 'raised_hands'}] } } # 调用被测函数,它内部会创建 SlackClient 实例(实际得到 mock) result = detect_raised_hand('C001', '1234567890.123456', 'xoxb-fake') assert result is True client.get_reactions.assert_called_once_with('C001', '1234567890.123456') #+end_src *关键点* : =patch('src.detector.SlackClient')= patch 的是被测代码中的引用位置(即 =src.detector= 模块里的 =SlackClient= 名称),而非原始定义位置。这样被测代码内部执行 =SlackClient(token)= 时,实际得到的是 mock 实例。 =MockClient.return_value= 就是这个 mock 实例——每次"实例化"得到的都是同一个 mock 对象。你可以用 =return_value= 设置方法的返回值,用 =assert_called_once_with= 验证调用参数。 * 策略二:辅助函数封装 mock 配置——适用于工厂模式的 SDK 有些 SDK 使用工厂模式:调用一个函数后才返回客户端实例。比如 Google Sheets SDK 的用法是 =build('sheets', 'v4', credentials=creds)= ——每次调用 =build= 都返回一个新的服务对象。 策略一能解决大部分场景,但遇到这种"深层链式调用 + 工厂函数"的库就麻烦了。 =build()= 返回的对象上有 =spreadsheets().values().batchGet().execute()= 这样的多层调用,手动一层一层设 =return_value= 非常繁琐。 解决方法:写一个辅助函数,把 mock 的层级结构封装起来,每个测试只需传入不同的数据。 #+begin_src python # tests/test_sheets.py from unittest.mock import MagicMock, patch from src.sheets import fetch_rehearsal_data def make_sheets_mock(batch_get_data): """创建一个配置好的 Sheets 服务 mock""" mock_service = MagicMock() # 设置完整的调用链:service.spreadsheets().values().batchGet().execute() mock_service.spreadsheets().values().batchGet().execute.return_value = { 'valueRanges': batch_get_data } return mock_service @patch('src.sheets.build') def test_cancelled_rehearsal(mock_build): # 工厂函数 build() 返回我们配置好的 mock mock_build.return_value = make_sheets_mock([ {'range': 'B1:I1', 'values': [['header1', 'header2']]}, {'range': 'B4:I4', 'values': [['Rehearsal cancelled', 'Run Through Title']]} ]) result = fetch_rehearsal_data() assert result[1]['values'][0][0] == 'Rehearsal cancelled' #+end_src 为什么这个方法有效?因为 =MagicMock= 有个天然特性:访问任何不存在的属性都会自动返回一个新的 =MagicMock= 。所以 =mock.spreadsheets().values().batchGet()= 这条链上的每一层都是一个自动创建的 mock 对象,最终你在 =execute= 上设置的 =return_value= 会被正确返回。 这比在 Jest 中用闭包变量 + setter 更直观——Python 的 mock 对象天然支持动态修改属性,不需要额外的 setter 函数来"穿透"工厂函数。 * 策略三:仓储类封装——适用于数据库 ORM 数据库 SDK 的 mock 难度最高,因为涉及查询构造器、连接管理、事务等复杂逻辑。直接 mock 数据库驱动往往会导致测试和实现细节强耦合——你改了查询写法,测试就挂了。 更好的做法是给数据库操作封装一个"仓储类"(Repository),让业务代码只通过仓储类访问数据库。然后在测试中 mock 仓储类的方法。这样测试只关心输入输出,不关心内部查询细节。 #+begin_src python # src/db.py class AttendanceRepo: def __init__(self, db_client): self.db = db_client def get_attendance(self, team_id): return self.db.collection(f'attendance-{team_id}').get() def save_attendance(self, team_id, data): self.db.collection(f'attendance-{team_id}').add(data) #+end_src 在测试中直接 mock 仓储类,不需要碰数据库驱动: #+begin_src python # tests/test_attendance.py from unittest.mock import MagicMock from src.db import AttendanceRepo from src.attendance import AttendanceService def test_notify_installer_when_post_not_found(): repo = MagicMock(spec=AttendanceRepo) repo.get_attendance.return_value = [] # 模拟空数据库 service = AttendanceService(repo=repo) service.update_message(team_id='T001', token='xoxb-xxx') repo.get_attendance.assert_called_once_with('T001') #+end_src =spec= 参数告诉 =MagicMock= 按照 =AttendanceRepo= 的接口创建 mock。好处是:如果你在 =AttendanceRepo= 上删除或重命名了某个方法,对应的测试会立即报错,而不是悄悄通过。 如果你确实需要模拟完整的数据库行为(而不仅仅是 mock 方法调用),可以用专门的 mock 库: - SQLite:直接用内存数据库 =sqlite3.connect(':memory:')= - MongoDB:用 =mongomock= 库,它实现了完整的 MongoDB 接口 - PostgreSQL:用 =testcontainers= 启动一个真实的临时 Docker 容器 *策略三的核心思路* :不是去 mock 底层数据库驱动的每个方法,而是把数据库访问抽象到一层,然后在测试中替换整个抽象层。这样即使以后换数据库(比如从 Firestore 换到 PostgreSQL),测试代码基本不用改。 * 策略四: =responses= 拦截 HTTP 请求——适用于 REST API 对于 =requests= 库发出的 HTTP 请求, =responses= 库能直接拦截并返回预设数据,不需要启动真实服务器。 #+begin_src python # tests/test_holiday.py import responses from src.holiday import is_bank_holiday @responses.activate def test_is_bank_holiday(): responses.add( responses.GET, 'https://www.gov.uk/bank-holidays.json', json={ 'england-and-wales': { 'events': [ {'title': "New Year's Day", 'date': '2024-01-01', 'bunting': True} ] } }, status=200 ) assert is_bank_holiday('2024-01-01') is True assert is_bank_holiday('2024-01-02') is False #+end_src =responses.activate= 装饰器会在测试期间拦截所有 =requests.get/post/...= 调用。 =responses.add= 注册一条规则:当请求匹配指定的 URL 和方法时,返回预设的响应。 需要注意, =responses= 只拦截 =requests= 库的调用。如果你用的是其他 HTTP 库,对应的选择不同: - =httpx= :用 =respx= 库 - =urllib= / 通用:用 =httpretty= 库 - =aiohttp= (异步):用 =aioresponses= 库 * 四种策略的选择指南 | 场景 | 推荐策略 | 核心工具 | |------+----------+----------| | 自己封装了客户端类 | 策略一 | =patch= + =MagicMock= | | SDK 有工厂函数和深层链式调用 | 策略二 | =MagicMock= 链 + 辅助函数 | | 数据库 ORM | 策略三 | 仓储类封装 + =spec= mock | | REST API( =requests= ) | 策略四 | =responses= | 选择的核心逻辑是看你要 mock 的依赖的 /接口形态/ :如果是普通类的方法,策略一用 =patch= 就够了;如果 SDK 有工厂函数和深层链式调用,策略二用辅助函数封装 mock 配置;如果涉及数据库,最好先抽象出仓储层再用策略三;如果是标准 HTTP 请求,策略四用 =responses= 最省事。 原文链接: [[https://localghost.dev/blog/different-ways-to-mock-third-party-integrations-in-jest/][Different ways to mock third-party integrations in Jest]]
❌
❌