English 简体中文 繁體中文 한국 사람 日本語 Deutsch русский بالعربية TÜRKÇE português คนไทย french
查看: 7|回复: 0

o3-mini、Gemini 2 Flash、Sonnet 3.5 与 DeepSeek 在 Cursor 上的对决

[复制链接]
查看: 7|回复: 0

o3-mini、Gemini 2 Flash、Sonnet 3.5 与 DeepSeek 在 Cursor 上的对决

[复制链接]
查看: 7|回复: 0

343

主题

0

回帖

1039

积分

金牌会员

积分
1039
33333mm

343

主题

0

回帖

1039

积分

金牌会员

积分
1039
2025-2-10 11:45:34 | 显示全部楼层 |阅读模式
最新的 OpenAI 模型 o3-mini 已于 1 月 31 日(星期五)发布,并已在 Cursor 上架。不久后,Gemini 2 Flash 也会陆续登场。
上周,对 DeepSeek V3、DeepSeek R1 以及 Claude 3.5 Sonnet 做过类似测试。那次测试结果显示,在日常开发中,Claude 3.5 Sonnet 的表现明显优于两个 DeepSeek 版本。不过,新模型上线后,自然得重新用相同任务对它们进行比较,同时为了好玩,也把两个 DeepSeek 模型的数据保留下来。
<hr>测试任务简介

此次测试主要涵盖三种模式:聊天(Chat)代码生成(Composer) 以及 代理模式(Agent Mode)。需要注意的是,目前代理模式仅支持 Anthropic 和 OpenAI 系列模型,其他模型暂不支持这一功能。
<hr>聊天任务

任务要求:
检查 CircleCI 部署配置,并说明在部署过程中如何将静态 NextJS 资源推送至 Cloudflare。提供的提示内容如下:


“解释在部署过程中如何将静态 NextJS 资源上传到 Cloudflare。”
(同时我还附上了 CircleCI 配置文件作为参考背景)
期望的回答应该包括:

  • 正确描述在部署中将静态资源送往 Cloudflare 的步骤;
  • 针对 NextJS 配置提出建议,说明如何使用 Cloudflare 作为 CDN。
o3-mini 的回答
它主要描述了如何配置 Cloudflare Pages,并利用 wrangler CLI 来部署静态资源。不过,实际上 Cloudflare Pages 并非最佳的 CDN 解决方案。它还提到了更新站点 DNS 或设置反向代理,但细节略显简略,而且没有指出 NextJS 配置中需要更新的部分。
Claude 3.5 Sonnet 的回答
Sonnet 给出的方案包括安装 AWS CLI 的步骤,并建议在 NextJS 配置中按如下方式修改:
const nextConfig = {  output: 'standalone',  assetPrefix: process.env.PUBLIC_ASSETS_BASE_URL,  // 其它配置项……}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.





同时,它推荐使用 Cloudflare R2,而没有提及 Cloudflare Pages。
Gemini 2 Flash 的回答
Gemini 同样建议选用 Cloudflare R2,并指出可能需要更新 assetPrefix,不过没有深入细说。它给出的 NextJS 配置示例如下:
const nextConfig = {  // 其它配置……  images: {    domains: ['your-site-static-assets-production.r2.dev', 'your-site-static-assets-qa.r2.dev'],  },};

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.





DeepSeek V3 的回答
DeepSeek V3 除了建议使用 Cloudflare R2,并清楚描述了如何更新 assetPrefix 外,还建议通过编写 TypeScript 辅助文件,再在 CircleCI 中通过 package.json 脚本执行上传操作。虽然这种做法并非错误,但相比直接使用 CLI 显得有些繁琐。
DeepSeek R1 的回答
R1 的方案与 Sonnet 几乎一模一样,仅在细节上有微小差别。
<hr>Composer 代码生成任务

在这部分,我提供了一段处理招聘网站相关功能的服务端代码,该代码用于获取雇主的招聘信息。任务要求是在原有的 getEmployers 服务端操作中增加分页和搜索功能,要求:

  • 能够对雇主名称进行模糊搜索;
  • 接受页码和条数限制;
  • 返回包含总记录数及是否有更多记录的元数据。
现有的代码如下:
export const getEmployers = actionClient.action(async () => {  const profile = await getActiveProfileOrThrowError();  if (profile.type !== "jobBoard") {      throw new Error("Unauthorized");  }  const applications = await db.query.employerJobBoardApplications.findMany({    where: eq(employerJobBoardApplications.jobBoardId, profile.id),    with: {      employer: true,    },  });  return applications;});

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.





预期输出应满足以下几点:

  • 识别出已有代码使用了 zod schema,因此新增部分也应跟进这一规范;
  • 高效地计算分页所需的元数据;
  • 对关联表的雇主名称进行正确的模糊查询。
o3-mini 的回答
它虽然花了一些时间,但在使用 zod schema 这一部分做得不错,也意识到模糊搜索应通过 inner join 来实现。不过,它选择用原生 SQL 语句进行模糊搜索,类似如下做法:
if (search) {  conditions.push(sql`"employer"."name" ILIKE ${`%${search}%`}`);}let totalRecords: number;if (search) {  const totalCountRes = await db    .select({ count: sql`count(*)` })    .from(employerJobBoardApplications)    .innerJoin(      employers,      eq(employerJobBoardApplications.employerId, employers.id),    )    .where(and(...conditions));  totalRecords = Number(totalCountRes[0]?.count ?? 0);} else {  const totalCountRes = await db    .select({ count: sql`count(*)` })    .from(employerJobBoardApplications)    .where(baseCondition);  totalRecords = Number(totalCountRes[0]?.count ?? 0);}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.





但这种方法在类型安全上不够理想,同时代码复用也有所欠缺。总体来看,效果一般,还需要进一步引导完善。
Claude 3.5 Sonnet 的回答
Sonnet 很好地识别了原代码中的 zod schema,并在新代码中沿用了这一规范。它将搜索与计数的 where 逻辑进行了复用,但在使用 Drizzle ORM 时,inner join 的处理上不够到位。示例代码如下:
const where = [eq(employerJobBoardApplications.jobBoardId, profile.id)];if (search) {  where.push(    ilike(employerJobBoardApplications.employer.name, `%${search}%`),  );}const [applications, totalCount] = await Promise.all([  db.query.employerJobBoardApplications.findMany({    where: and(...where),    with: {      employer: true,    },    limit,    offset,  }),  db.query.employerJobBoardApplications.count({    where: and(...where),  }),]);

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.





在我提示要求使用 inner join 后,它也做出了相应调整。
Gemini 2 Flash 的回答
这款模型反应迅速,采用了 zod schema,但和 Sonnet 类似,在 inner join 这一环节处理不当,其代码示例如下:
const [applications, total] = await Promise.all([  db.query.employerJobBoardApplications.findMany({    where: and(...whereConditions),    with: {      employer: true,    },    limit,    offset,  }),  db    .select({      count: sql`count(*)`.mapWith(Number),    })    .from(employerJobBoardApplications)    .where(and(...whereConditions))    .limit(1)    .then((res) => res[0]?.count ?? 0),]);

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.





DeepSeek V3 的回答
DeepSeek V3 同样正确使用了 zod schema,但在 inner join 方面没有达到预期效果。它还重复书写了搜索和计数的 where 逻辑,没有做到代码共享:
// 获取总记录数const totalCount = await db  .select({ count: sql`count(*)` })  .from(employerJobBoardApplications)  .where(    and(      eq(employerJobBoardApplications.jobBoardId, profile.id),      search        ? ilike(employerJobBoardApplications.employer.name, `%${search}%`)        : undefined,    ),  )  .then((res) => res[0]?.count ?? 0);// 分页查询结果const applications = await db.query.employerJobBoardApplications.findMany({  where: and(    eq(employerJobBoardApplications.jobBoardId, profile.id),    search      ? ilike(employerJobBoardApplications.employer.name, `%${search}%`)      : undefined,  ),  with: {    employer: true,  },  limit,  offset,});

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.





DeepSeek R1 的回答
R1 同样使用了 zod schema,但尽管思考得较多,inner join 的实现还是未能完善,需要进一步提示以优化 where 逻辑的复用。示例代码如下:
// 主查询:分页和搜索const data = await db.query.employerJobBoardApplications.findMany({  where: (apps, { and, eq, or }) =>    and(      eq(apps.jobBoardId, profile.id),      search ? ilike(apps.employer.name, `%${search}%`) : undefined,    ),  with: {    employer: true,  },  offset,  limit,});// 总记录数查询const [countResult] = await db  .select({    count: sql`count(*)`,  })  .from(employerJobBoardApplications)  .where(    and(      eq(employerJobBoardApplications.jobBoardId, profile.id),      search        ? ilike(employerJobBoardApplications.employer.name, `%${search}%`)        : undefined,    ),  );

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.





<hr>代理模式任务

在代理模式测试中,我希望模型完成一个更复杂、多步骤的任务:在一个项目模板中增加新用户引导流程。要求如下:

  • 在用户表中添加三个字段:布尔类型的 isOnboardingComplete、字符串类型的 onboardingPersona,以及 JSON 字符串数组 onboardingTopics;
  • 当用户登录且未完成引导时,页面上应弹出一个对话框,该对话框内包含相应的表单供用户选择;
  • 表单提交后,通过服务端操作(采用 next-safe-action)更新引导状态。
需要注意的是,用户表定义在 Drizzle ORM 的 schema 文件中,模型需要自动找到并修改相关定义,同时确保引导流程能够正常工作,且 next-safe-action 的使用与项目中其它部分保持一致。
o3-mini 的回答
o3-mini 在这部分的表现较差。首先,它响应较慢,可能是内部“思考”时间过长,而非网络问题。第一次尝试时,输出似乎中途截断,最后一句像是:“接下来我将更新用户表 schema 来禁用针对 JSON 列的 linter 错误……”,显然未完成;第二次尝试时,则发现生成结果仅在部分地方停留在提示状态,例如:“对于对话框,你可以这样实现……”,给出了占位符示例,但任务并未完全实现。
此外,第一次生成的方案中存在一些明显问题:

  • 文件被直接放在 monorepo 根目录,而预期应该在 next-app 目录下;
  • 自动生成了一个 global.d.ts 文件,用以定义 drizzle-orm 等包的类型,但在正确的 monorepo 结构中其实并不需要;
  • 生成的服务端操作未沿用项目中统一的 zod schema;
  • 对话框组件虽然正确调用了 Shadcn UI 组件,但却采用了内联样式,而非项目中普遍使用的 tailwind 类。
整体来看,o3-mini 在处理 monorepo 环境时明显遇到了困难。
Claude 3.5 Sonnet 的回答
Sonnet 对用户表 schema 的修改做得正确,为实现对话框功能,它选择在整个应用外层包裹一个包装组件,其示例代码如下:
export function OnboardingWrapper({ children }: Props) {  const { isOpen } = useOnboarding();  return (                {children}      );}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.





包装组件中用到的 useOnboarding 钩子定义如下:
import { useEffect, useState } from "react";import { getUser } from "../actions/user";export function useOnboarding() {  const [isOpen, setIsOpen] = useState(false);  useEffect(() => {    const checkOnboarding = async () => {      const user = await getUser();      if (user && !user.isOnboardingComplete) {        setIsOpen(true);      }    };    checkOnboarding();  }, []);  return { isOpen };}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.





不过,这里有个问题:直接在钩子中调用服务端操作是不被允许的(除非该操作是通过 next-safe-action 封装的)。此外,这种实现会导致页面首次加载时延迟显示对话框,等 getUser 请求完成后才出现。好在对话框组件本身表现不错,且 next-safe-action 的用法也正确;它甚至试图使用 Select 组件来适应前端的 Shadcn UI 风格(尽管项目中尚未加入该组件)。生成的服务端操作代码基本无误,但在 next-safe-action 的语法上略有偏差,建议参照项目中已有用法作出调整。
DeepSeek 与 Gemini 2 Flash(代理模式)
目前这两款模型在 Cursor 平台上还不支持代理模式,这部分测试只能留待未来补充。
<hr>总结

虽然对 o3-mini 和 Gemini 2 Flash 都充满期待,但在实际开发中的表现并没有超出预期。所有模型在处理这些实际任务时都有各自的不足,连 Claude 3.5 Sonnet 也不例外,实际效果与各类公开的编码基准测试结果存在明显落差。特别是在代理模式测试中,o3-mini 在 monorepo 环境下的表现不佳。由于经常依赖代理模式,并且非常喜欢 monorepo 架构,目前的选择仍会倾向于使用 Claude 3.5 Sonnet。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

343

主题

0

回帖

1039

积分

金牌会员

积分
1039

QQ|智能设备 | 粤ICP备2024353841号-1

GMT+8, 2025-3-10 14:58 , Processed in 0.812096 second(s), 27 queries .

Powered by 智能设备

©2025

|网站地图