最近在排查一个前后端错配问题时,我顺便拿它来测了几款大模型的“分析能力”。结果挺有意思:有的模型一下就抓住了关键,有的模型则被自己的经验带偏。今天把这个过程写出来,算是一点复盘,也顺便聊聊未来模型选型的思路。


背景:一个典型的“伪异步”问题

场景是这样的:
前端写的逻辑是「检查缓存 → 创建任务(带 task_id)→ 轮询进度 → 获取结果」。
看上去很标准,但后端的实现却是:getInfo 接口里直接调用了同步的评估 RPC,一直阻塞到结果返回。结果就是:

  • 前端以为在异步轮询,其实请求早就被挂起。
  • 轮询接口形同虚设,只能等同步调用结束。
  • 小数据集下好像没问题,大数据集就暴露了。

这就是典型的“伪异步”。


谁分析对了,谁走偏了?

我把这个问题分别丢给了 GPT-5、Grok-4、Claude 3.7、Claude 4.0 sonnet、Gemini 2.5。结果很分化:

  • GPT-5 think ✅
    分析完整,先指出“SwGetModelInfo 是同步阻塞”,然后给出两条一致性方案:要么后端真异步,要么前端收敛为同步。还给了“最小改造集合”和 UI 层的优化建议。属于工程师看了就能落地的答案。

  • Grok-4 think ✅
    逻辑清晰,把前端三段式和后端阻塞实现对照拆开,指出这就是“伪异步”。也分析了可能的历史演进原因,思路很接近真实团队复盘。

  • Claude 3.7 ✅
    风格简洁,直接说“没有 go,就是同步阻塞”。一针见血,但没有展开改造方案。适合当“关键点复核”,但深度稍欠。

  • Claude 4.0 sonnet ❌
    假设“带 task_id 就一定是异步”,然后推了一套看似合理的故事,但代码里根本没那个逻辑。典型的“以常识代替实现”。

  • Gemini 2.5 ❌
    构造了一个“后台 Goroutine + 进度写库”的故事,看起来自洽,其实和代码完全不符。算是“细节型幻觉”。


对比总结:不同模型的分析风格

这次测试下来,不同模型展现了很鲜明的风格:

模型 风格 优点 局限
GPT-5 系统化、工程可落地 方案完整,能直接改代码 输出较长,需要耐心读
Grok-4 结构清晰、逻辑自洽 前后端对齐分析强 偶尔会假设后端异步,但能回滚
Claude 3.7 极简、抓关键 一针见血、证据指向性强 覆盖不全,方案不足
Claude 4.0 Sonnet 表达流畅、结构化强 在写作、总结、解释复杂概念方面表现优秀 在依赖代码证据的场景里容易经验化推断
Gemini 2.5 细节导向、推理链条完整 在复杂场景里能给出较细的分析脉络 偶尔会像 Claude 一样走经验捷径,出现“细节型幻觉”

可以看到:

  • GPT-5Grok-4:更适合作为工程问题的“主力分析模型”,能跑通从证据到落地方案的闭环。
  • Claude 3.7:像一个“挑错助手”,虽然思路简洁,但在关键点识别上非常敏锐。
  • Claude 4.0 Sonnet:在这个案例里因经验套用失手,但整体上是很强的模型,尤其适合总结文档、表达复杂逻辑、做解释型任务
  • Gemini 2.5:这次也因为经验套用出错,但它的分析推理能力本身不弱,而且在表达和细节展开上有独特价值,未来如果配合证据驱动会非常有潜力。

我的启发:未来选型的思路

所以选型上,我的想法是:

  • 工程排查/代码分析类场景:以 GPT-5 / Grok-4 为主力,Claude 3.7 辅助挑关键。
  • 总结/写作/解释类场景:Claude 4.0 Sonnet 和 Gemini 都是很好的主力选择,GPT-5 辅助保证逻辑严谨。
  • 创意/故事化场景:Gemini 表现出色,Claude 在结构化表达上也能辅助。

换句话说,没有哪个模型是“放之四海而皆准”的,合理的搭配使用,才是发挥它们价值的关键。


各模型回复

Claude 4.0 sonnet think

你发现了一个关键问题!让我重新分析这个逻辑

经过深入分析代码,我发现这个设计实际上是有其内在逻辑的,但确实容易引起混淆:

真正的执行逻辑

  1. 后端 gRPC 引擎支持异步任务 从 gRPC proto 文件可以看出,引擎在 v1.1.5 版本添加了 task_id 参数和 GetTaskProceed 接口用于轮询进度。
  2. SwGetModelInfo 的两种工作模式
    • 不带 task_id:同步执行或返回缓存结果
    • task_id:启动异步任务,立即返回,任务在后台执行
  3. 前端的分阶段调用策略
// 第一步:检查缓存(不带task_id)
  // 有缓存,直接使用
} else {
  // 第二步:启动异步任务(带task_id)
  // 第三步:轮询任务进度
  // 第四步:获取最终结果(不带task_id)
}

关键理解

task_id GetInfo 调用

  • 这个调用是为了启动异步任务,而不是获取结果
  • 后端引擎接收到 task_id 后,会启动后台计算进程
  • 前端立即开始轮询进度,不等待这个接口返回完整结果

问题所在: 前端代码中,带 task_id 的调用确实可能会直接返回结果(如果计算很快),但设计意图是启动异步任务。这就是为什么后面还有最终的结果获取步骤。


GPT-5 think

  • 你的判断是对的:前端现在的“轮询”设计与后端实现不匹配。
  • 证据:
    • 前端在 toAssess 里,当无缓存时生成 task_id,先 GET modelApi.getInfo 发起“任务”,随后调用 pollTaskProgress(taskId) 循环查询 modelApi.task_proceed
    • 后端在 controllers/feature_extract.gogetInfo 流程中,调用的是 swgrpc.GrpcEngine.SwGetModelInfo(..., taskID),而从你给的片段看,该调用是同步阻塞的(一次 RPC 返回即得到 cllr、score 等完整结果)。
    • 因为后端同步执行且返回完整结果,前端所谓的“创建任务+轮询进度”事实上并不存在有效的异步任务。若后端未真正把 task_id 注入一个后台队列并异步处理,那么“轮询进度”永远可能查询不到阶段性进度,只会在前端等待 RPC 实际完成后才有结果。
  • 现状可能出现的问题:
    • 前端发起第一次 getInfo 时,接口会阻塞直到评估完成;同时前端还在轮询 task_proceed,但后端并没有对应的进度更新,从而显示“正在处理”但进度一直不变,或者返回失败。
    • 如果网络/超时时间较短,前端可能先超时,造成 UX 糟糕。
  • 可选改造方向(两种一致性策略,选其一):
    xxxx

Grok 4 think

是的,你的观察很敏锐,这个逻辑确实存在一些潜在问题和不一致性。下面我基于代码分析一步步解释为什么会出现这种情况,以及可能的原因和建议。分析基于前端(ModelInfo.vue)和后端的代码逻辑。

  1. 前端逻辑回顾(toAssess 方法)

前端在处理测试数据集评估时,采用了“缓存检查 + 任务创建 + 轮询 + 最终获取”的流程:

  • 步骤1:检查缓存
  • 步骤2:如果无缓存,创建任务
  • 步骤3:轮询进度
  • 步骤4:获取最终结果

这个设计假设后端支持异步任务:带 task_id 时不阻塞,立即返回任务启动确认;不带 task_id 时检查/返回结果。

  1. 后端逻辑分析

后端的 GetInfo 接口是模型评估的核心入口:

  • 参数处理:接收 id(模型ID)、data_set_id(数据集ID)和可选的 task_idonly_info
  • 缓存检查:先查询数据库(testCalibrationModel.Exist()),如果存在缓存,直接返回缓存数据(包括 CLLR、图片路径等)。
  • 无缓存时的处理
    • 如果 !only_info(前端调用时未指定,默认为 false),直接调用 swgrpc.GrpcEngine.SwGetModelInfo(这是一个同步 gRPC 调用,传入 taskID 如果有)。
    • 调用后,立即处理结果(如果成功,存入数据库作为新缓存,然后返回数据;如果失败,返回错误)。
  • 关键点:无论是否带 task_id,后端都会同步执行 SwGetModelInfo,等待 gRPC 返回结果。没有根据 task_id 切换到异步模式的逻辑。它总是阻塞直到计算完成或出错。

从 gRPC 包装层看:

  • SwGetModelInfo 是对 gRPC 客户端的同步调用(g.client.GetModelInfo(ctx, request))。
  • 它会等待服务端响应,没有异步机制。即使传入 taskID,它也只是作为请求参数传递给 gRPC 服务端,但从代码看,没有特殊的异步处理(可能是 gRPC 服务端内部实现异步,但从提供的代码片段无法确认)。

此外,后端有独立的 GetTaskProceed 接口(对应前端的轮询),它调用 SwGetTaskProceed 获取任务进度。但在 GetInfo 中,并没有启动后台任务的代码——它直接同步计算。

  1. 为什么这个逻辑有问题?
    xxx

  2. 可能的原因 xxxx

  3. 建议修复 xxx


Claude 3.7 sonnet

您提出了一个非常好的问题。确实,这里存在明显的设计逻辑问题:

从您提供的控制器代码片段可以看出,后端的调用是同步的:

这里存在的问题:

  1. 同步调用与异步轮询矛盾:后端的 SwGetModelInfo 是同步调用,会阻塞直到计算完成并返回结果。这意味着当前端收到响应时,评估已经完成了,没有必要再轮询任务状态。
  2. 任务 ID 可能没有真正使用:虽然前端生成了 taskId 并传递给了后端,但如果 SwGetModelInfo 是同步的,那么任务 ID 可能没有被用于实际的异步任务跟踪。
  3. 轮询机制可能是无效的:前端在调用 getInfo API 后立即开始轮询 task_proceed API,但如果 getInfo 是同步的,那么轮询机制完全是多余的。
    正确的实现应该是:xxx
    这种设计上的不一致可能导致:xxx

Gemini 2.5 pro preview 0605

您提出了一个非常关键且很好的问题!从表面上看,前端在轮询,而后端的核心 gRPC 调用 SwGetModelInfo 又是同步阻塞的,这看起来确实是矛盾的。

您是对的,如果 getInfo 接口在收到请求后,直接调用 SwGetModelInfo 并等待其返回,那么前端的轮询机制将毫无意义,因为第一个 POST 请求就会被一直阻塞,直到评估完成。

但实际情况更复杂一些,这里的关键在于后端 getInfo 接口根据是否传入 task_id 执行了两种完全不同的逻辑,从而实现了一种“异步任务”模式。

我来为您梳理一下完整的、真实的执行流程:

核心解惑:后端如何实现“假异步”:xxxx
前后端完整交互流程:xxxx
总结

您的观察非常敏锐,SwGetModelInfo 本身确实是同步的。这个架构的关键点在于 Go 后端利用协程 (Goroutine) 将这个同步的、耗时的操作包装成了一个异步执行的任务getInfo 接口通过是否传入 task_id 来区分“触发任务”和“查询结果”,再配合一个专门的 task_proceed 接口来查询进度,从而完美地实现了前后端的非阻塞式任务处理流程。

所以,前端的轮询逻辑是完全正确且必要的,因为它恰好匹配了后端精心设计的这种异步任务处理模式。

Logo

更多推荐