1. 为什么我不再只看跑分选模型
跑分这东西,我早年也迷信过。HumanEval 刷到 90% 以上、MBPP 接近满分,看着确实提气,但真把它丢进一个稍微有点历史包袱的仓库里,让它改一个跨了四个模块的 bug,结果往往是:单测能过,集成就炸。原因不复杂——跑分题大多是自包含的小函数,输入输出边界清晰,而真实工程里的代码是"活的",有隐式约定、有历史遗留、有上下游耦合。
Step 5 Preview 这个模型出来的时候,官方给的几个数字挺抓眼球:MoE 架构、1M Token 上下文、支持视觉输入。但我更关心的是它在真实编程任务里到底能不能打。所以我没去跑 benchmark,而是直接挑了两个我自己手头真实存在的任务来测:一个是给一个中型 Python 服务做跨模块重构,另一个是让模型读一张架构草图然后生成对应的接口代码。前者考的是长上下文和代码理解,后者考的是视觉输入到底是不是噱头。
这篇文章就是这两个任务的完整实测记录。我会把任务背景、我怎么写的提示词、模型返回了什么、哪里翻车了、哪里超出预期,全部摊开讲。适合正在选 AI 编程助手的人、想搞清楚 MoE 和长上下文到底意味着什么的人,以及那些跟我一样对跑分已经脱敏的人。
先说结论方向:Step 5 Preview 在长上下文场景下的表现确实和普通模型拉开了差距,但视觉输入这块,能用,但别指望它替你做设计决策。具体怎么个"能用"法,往下看。
2. 先搞清楚 Step 5 Preview 的几个关键特性
2.1 MoE 架构到底改变了什么
MoE,Mixture of Experts,混合专家。这个词这两年满天飞,但很多人对它的理解停留在"参数多但激活少"。我用一个更直白的类比:传统稠密模型像一家所有科室都全天坐诊的医院,你去看个感冒,心内科、骨科、神经科的医生也都在上班,只是没给你看。MoE 则是分诊台先判断你挂哪个科,只叫相关科室的医生来。总参数可以堆得很大,但每次推理实际参与计算的只是一部分。
这对编程任务意味着什么?编程是一个高度依赖"精确召回"的场景。你问它一个 API 用法,它需要准确调出那个库的知识;你让它改一段代码,它需要准确理解那段代码的语义。MoE 的路由机制如果训得好,理论上能让不同专家分别擅长不同语言、不同框架、不同任务类型,从而在总容量很大的情况下保持推理效率。
但这里有个常见的误解我得先破掉:MoE 架构并不意味着全部参数都要进显存。这是热搜里"moe架构要全部参数进显存吗"这个问题的高频疑惑。答案是:推理时通常只需要加载全部专家权重(因为路由是动态的,你没法预知这次会激活哪些专家),但计算时只有被选中的专家参与前向。所以显存占用看的是总参数量,算力消耗看的是激活参数量。这两件事要分开算。如果你本地部署,显存按总参数估;如果你用 API,那基本不用操心这个,按 token 计费就行。
2.2 1M Token 上下文在编程里意味着什么
1M Token 是个什么概念?大概相当于 70 万到 80 万个英文单词,或者一个中等规模代码仓库的核心部分。对编程任务来说,这个能力的价值不在于"能塞进去",而在于"塞进去之后还能不能准确用"。
我见过太多模型,上下文窗口标得很大,但你真塞个几万 token 进去,它就开始"中间迷失"——开头和结尾记得住,中间的内容当没看见。这在编程里是致命的,因为一个函数的定义可能在文件开头,调用在文件末尾,中间夹着几百行无关代码。如果模型对中间部分失忆,它就会给出一个"看起来对但实际调用了不存在的方法"的答案。
所以测长上下文,不能只测"能不能塞",要测"塞进去之后跨文件、跨模块的引用关系它能不能理清"。这也是我第一个任务的设计初衷。
2.3 视觉输入:是刚需还是锦上添花
视觉输入这个能力,在编程场景里其实比很多人想的更有用。最常见的场景是:产品经理甩给你一张手绘的流程图或者白板照片,让你照着实现。以前你得自己翻译成文字描述再喂给模型,现在可以直接把图丢进去。
但视觉输入有个隐藏的坑:模型对图的"理解"和对代码的"生成"是两套能力。它可能准确识别出图里画了三个框和两条箭头,但生成代码时把箭头方向搞反。所以这个能力我建议定位成"辅助理解",而不是"自动实现"。具体实测见第四个章节。
3. 任务一:跨模块重构实测
3.1 任务背景与代码规模
我手头有一个 Python 服务,大概 1.2 万行代码,分在 40 多个文件里。核心逻辑是一个订单处理流程,早期为了赶进度,把校验、计算、持久化全塞在一个 800 多行的order_service.py里。现在要做的重构是:把校验逻辑抽到validators/目录,把金额计算抽到pricing/目录,把持久化相关抽到repositories/,同时保持对外接口不变。
这个任务的难点在于:调用方散落在十几个文件里,有些调用是直接 import 那个大文件里的函数,有些是通过类方法调的。重构的时候如果漏掉任何一处,运行时就报 ImportError 或者 AttributeError。这正是考验长上下文和跨文件理解能力的场景。
我把整个仓库的相关文件(大概 30 个文件,合计约 8 万 token)一次性喂给了 Step 5 Preview,然后给了它一段提示词。提示词我写得比较克制,没有一上来就要求它改代码,而是先让它做分析。
3.2 提示词是怎么写的
我用的提示词大致是这样的结构:
你是一个有十年经验的 Python 后端工程师。下面是一个订单服务的代码仓库, 我打算做一次重构,把 order_service.py 里的职责拆分出去。 第一步:先不要改代码。请分析 order_service.py 里有哪些函数, 按职责分类,并列出每个函数被哪些文件、以什么方式调用。 输出一个表格,包含:函数名、职责分类、调用方文件、调用方式。 第二步:基于你的分析,给出一个拆分方案,说明每个新模块应该包含哪些函数, 以及拆分后需要修改哪些调用方。 注意:不要遗漏任何调用点,包括通过字符串动态调用的地方。我特意把任务拆成两步,先分析后动手。这是我从踩坑里总结出来的经验:直接让模型改代码,它容易"边改边想",改到一半发现前面的假设错了,但已经改了一堆文件,最后给你一个半成品。先让它把依赖关系理清楚,我再人工核对一遍,确认没问题了再让它动手,成功率会高很多。
3.3 模型返回的分析结果
模型返回的表格让我有点意外。它不仅列出了显式调用,还标出了两处通过getattr动态调用的地方——这两处我自己都差点忘了。表格大概长这样(我摘录一部分):
| 函数名 | 职责分类 | 调用方文件 | 调用方式 |
|---|---|---|---|
| validate_order | 校验 | api/views.py | 直接 import |
| validate_order | 校验 | tasks/async_job.py | getattr 动态调用 |
| calc_total | 金额计算 | api/views.py | 直接 import |
| calc_total | 金额计算 | order_service.py 内部 | 方法内调用 |
| save_order | 持久化 | api/views.py | 直接 import |
| save_order | 持久化 | management/commands/repair.py | 直接 import |
我核对了一下,动态调用那两处确实存在,而且是我自己写的时候留下的"技术债"。模型能揪出来,说明它确实把整个上下文都读进去了,没有中间迷失。
3.4 拆分方案与执行
分析通过之后,我让它给出拆分方案。它建议的目录结构和我预想的差不多,但有一个细节值得说:它建议在validators/__init__.py里做一层 re-export,把原来order_service里的函数名继续暴露出来,这样即使有调用方没改到,也不会立刻炸。
这个建议很务实。我原本的想法是"全部改干净",但模型提醒我:重构的第一原则是先保证不破坏,再谈优雅。它建议的过渡方案是:
# validators/__init__.py from .order_validator import validate_order from .amount_validator import validate_amount __all__ = ["validate_order", "validate_amount"]然后在order_service.py里保留一行from validators import *,这样老的调用方还能用,等所有调用方都迁移完了再删掉这行。这个"兼容层"的思路,说实话比我一开始的方案更稳。
执行阶段我让它分批改,每改完一个模块就停下来让我 review。整个过程大概来回五六轮,最终改完的代码我跑了全量单测,通过率 100%,没有出现 ImportError。
3.5 这个任务里踩到的坑
第一个坑是上下文太长导致的响应变慢。8 万 token 的输入,模型首次响应大概等了 20 多秒。这个延迟在交互式编程里是能感知的,所以我的建议是:长上下文适合做"分析型"任务,不适合做"高频交互型"任务。你要是想让它帮你补全一行代码,别把整个仓库塞进去。
第二个坑是模型对"隐式约定"的理解有限。比如我的代码里有个约定:所有金额计算必须用Decimal,不能用float。这个约定没有写在任何注释里,是我团队的口头规范。模型在生成新代码时,有一处用了float,被我 review 时抓出来了。所以长上下文能帮它理解"代码写了什么",但理解不了"团队约定是什么",这类信息你得在提示词里显式说明。
4. 任务二:视觉输入生成接口代码实测
4.1 测试用的图与预期
第二个任务我准备了一张手绘的架构草图,画的是一个简单的用户积分系统:三个方框分别是"API 层"、"积分服务"、"积分存储",箭头从 API 指向服务,从服务指向存储,另外有一条从服务回到 API 的虚线箭头,标注"异步回调"。
我的预期是:模型能识别出这三个组件和它们之间的调用关系,然后生成对应的接口定义(Python 的抽象基类或者 Protocol),把方法签名和调用方向体现出来。
4.2 实际生成结果
模型确实识别出了三个组件,也识别出了调用方向。它生成的代码大概是这样:
from typing import Protocol from decimal import Decimal class PointsStorage(Protocol): def get_balance(self, user_id: str) -> Decimal: ... def add_points(self, user_id: str, amount: Decimal) -> None: ... class PointsService(Protocol): def __init__(self, storage: PointsStorage): ... def earn(self, user_id: str, amount: Decimal) -> None: ... def redeem(self, user_id: str, amount: Decimal) -> bool: ... class PointsAPI(Protocol): def __init__(self, service: PointsService): ... def handle_earn(self, user_id: str, amount: Decimal) -> dict: ... def handle_redeem(self, user_id: str, amount: Decimal) -> dict: ...结构上没问题,依赖方向也对。但有几个细节它没处理好:
第一,那条"异步回调"的虚线箭头,它没有体现出来。我原本期待它能在PointsService里加一个回调接口,或者用asyncio相关的东西表达异步。它完全忽略了这条线。
第二,它自作主张加了earn和redeem两个方法。我的图里只画了组件和调用关系,没有画具体方法。它这是根据"积分系统"这个语义自己脑补的。脑补得合理,但严格来说超出了图的表达范围。
第三,Decimal这个类型它用对了,说明它从我的提示词里读到了"金额相关用 Decimal"这个约束。这点值得肯定。
4.3 视觉输入的正确用法
这次实测让我对视觉输入有了更清醒的认识。它擅长的是结构识别:几个框、几条线、谁指向谁,这些它能看准。它不擅长的是语义补全:虚线代表异步、某个框是外部系统、某条线是可选路径,这些图里没有明说的信息,它要么忽略,要么瞎猜。
所以我的用法建议是:把图当作"结构骨架"喂给它,然后在提示词里把图里没画的语义补上。比如我会这样写:
这张图里有一条虚线箭头从积分服务指向 API 层,代表异步回调。 请在生成代码时用一个回调接口来表达这个关系,不要忽略。 另外,图里没有画具体方法,你只需要生成组件之间的依赖关系和方法签名占位即可, 不要自己发明业务方法。加上这段约束之后,它第二次生成的结果就靠谱多了,虚线箭头被表达成了一个on_points_changed的回调 Protocol。
5. 两个任务横向对比与选型建议
5.1 能力对比表
| 能力维度 | 任务一(跨模块重构) | 任务二(视觉生成接口) |
|---|---|---|
| 长上下文理解 | 优秀,8 万 token 无迷失 | 不涉及 |
| 跨文件引用追踪 | 优秀,揪出动态调用 | 不涉及 |
| 视觉结构识别 | 不涉及 | 良好,组件和方向准确 |
| 语义补全 | 一般,需显式约束 | 较弱,容易脑补 |
| 代码生成质量 | 良好,需人工 review | 良好,需提示词约束 |
| 响应延迟 | 较高(长上下文) | 较低 |
5.2 什么场景该用它
基于这两个任务,我的判断是:Step 5 Preview 最适合的场景是中大型代码库的理解与重构。1M 上下文加上 MoE 的容量优势,让它在"需要同时看很多文件才能做决策"的任务上明显强于普通模型。如果你手头有一个祖传代码库要接手,或者要做一次影响面较大的重构,它值得一试。
视觉输入这块,我建议定位成"辅助工具"。它适合帮你快速把草图转成代码骨架,但骨架之后的填充和决策,还是得人来。别指望它看着一张图就给你一个能上线的系统。
5.3 和常见 AI 编程工具的搭配思路
现在市面上的 AI 编程工具不少,Cursor、Windsurf、VS Code Copilot、Trae 各有侧重。我的用法是分层:
- 行内补全用 Copilot 或 Cursor 的 Tab 补全,快,不打断思路。
- 单文件重构用 Cursor 的 Composer 或者 Windsurf,交互快。
- 跨仓库、跨模块的大任务,用 Step 5 Preview 这种长上下文模型,先分析后动手。
- 视觉相关的任务,把图丢给支持视觉的模型做初稿,再人工细化。
这个分层的关键逻辑是:任务越重、上下文越长,越要用长上下文模型;任务越轻、越频繁,越要用低延迟工具。别拿大炮打蚊子,也别拿弹弓打坦克。
6. 实操中的常见问题与排查
6.1 长上下文任务响应慢怎么办
这是最常见的抱怨。我的处理办法是:把任务拆成"分析"和"执行"两个阶段,分析阶段用全量上下文,执行阶段只喂相关文件。比如重构任务,分析时喂 30 个文件,执行时每次只喂要改的那 3 到 5 个文件加上它们的直接依赖。这样既保证了分析的准确性,又控制了执行阶段的延迟。
6.2 模型生成的代码用了不存在的 API
这个问题的根源通常是模型在长上下文里"记混了"。它可能把 A 文件的函数名安到了 B 文件上。排查方法是:让它生成代码后,自己再问一句"请列出你这次生成中用到的所有外部函数和类,并标注它们定义在哪个文件"。如果它标不出来,或者标错了,那这段代码就得重点 review。
6.3 视觉输入识别错误怎么补救
如果模型把图里的箭头方向搞反了,别急着重新生成,先用文字把正确的关系描述一遍,让它基于文字修正。视觉输入的错误往往是"看错了",而不是"理解错了",用文字补一刀通常能救回来。
6.4 提示词里必须显式说明的几类信息
从这两个任务里我总结出,有几类信息模型是猜不出来的,必须在提示词里写清楚:
- 团队约定:比如金额用 Decimal、日志必须带 trace_id、异常必须包装成自定义类型。
- 图里没画的语义:比如虚线代表异步、某个组件是外部依赖。
- 边界条件:比如"不要修改对外接口签名"、"不要引入新的第三方库"。
- 输出格式:比如"先输出分析表格,等我确认后再改代码"。
这几类信息不写,模型就会按它的"常识"来,而它的常识和你的项目现实往往有差距。
7. 我个人的几点使用体会
用 Step 5 Preview 做完这两个任务之后,我最大的感受是:模型的能力边界,很大程度上取决于你怎么用它。同一个模型,提示词写得糙,它就是个高级自动补全;提示词写得细,它能当一个能读懂整个仓库的初级工程师用。
MoE 和 1M 上下文这些特性,不是拿来炫技的,是拿来解决具体问题的。跨模块重构这种任务,以前我得自己画依赖图、自己列改动清单,现在可以让模型先跑一遍,我再核对。省下来的时间不是一点半点。
视觉输入我目前用得还比较保守,主要拿它做草图转骨架。等哪天它能把"虚线代表异步"这种图例也自动识别了,我再考虑让它多承担一点。
最后分享一个我自己的提示词模板,做重构类任务时基本够用:
你是一个资深 [语言] 工程师。下面是一个代码仓库的相关文件。 任务:[一句话描述重构目标] 约束: 1. 不要修改对外接口签名 2. 不要引入新的第三方依赖 3. [团队约定,如金额用 Decimal] 步骤: 第一步:分析现有代码结构,列出需要改动的函数和它们的调用方,输出表格。 第二步:等我确认后,给出拆分方案。 第三步:分批修改,每批改完停下来等我 review。 注意:不要遗漏动态调用(getattr、字符串反射等)。这个模板的核心是"先分析、后动手、分批执行",能挡掉大部分翻车情况。你可以根据自己的项目往里加约束,但别把步骤省了。