MoreRSS

site iconbang修改

JSPatch 开发者。88年生于广东陆丰,毕业于华南师范大学,程序员,喜欢做东西。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

bang的 RSS 预览

GTC 2025 见闻

2025-03-28 21:50:29

参加了 NVidia GTC (GPU Technology Conference),由于英伟达的地位,这会也已经成了 AI 开发者最大的交流会,很多公司和业内人士都会过来分享、交流,大概写下会议中相关见闻感受。

Keynote

老黄没提词器洋洋洒洒讲了两个多小时,出了小状况还会开个小玩笑,大佬范很足,也满满的理工男既视感,非常多的数字和未经包装的细节,不过感觉会讲得有些啰嗦。

总的来说,核心论证的是世界对 GPU 诉求会越来越大,而 NVidia 在 GPU 这个领域会持续遥遥领先

GPU诉求

计算机的核心从 CPU 转向 GPU,上个时代依靠程序员写代码指挥 CPU 执行指令解决问题,构成了现在庞大的 IT 产业,程序员是中心。现在的时代逐渐转变,GPU 生产的 token 逐渐能解决越来越多的问题,能思考,能生成代码指挥 CPU 去执行解决问题,计算的核心一定会转向 GPU,世界对 GPU 的需求只会越来越高。

给 AI 分了四个阶段,Perception AI → Generative AI → Agentic AI → Physical AI,不是很认同,Agentic 和 Physical 都是 Generative AI 的延续,不过无所谓,可以看到 Agentic 这个概念实在是火爆。

Scaling Law 没有停止,Agentic AI 需要深度思考,深度思考有新的 Test-time Scaling Law,越多的 token 输出效果越好需,要多轮理解和工具调用对 token 的消耗更是指数级上涨。

Physical AI 要更好地理解现实世界,声音/视觉/触感,都会比纯文本思考对 token 消耗的诉求更高,像 2G 时代看文字新闻,3G 4G 图片,5G 视频一样。

这两个发展中的领域对 GPU 的需求只会越来越高,Deepseek 做的优化也不足以影响这个需求的增长,这个市场不容质疑。

NVidia 优势

GPU 需求量是高,但未来大家一定会买 NVidia 卡吗?当然。NVidia 这一代 blackwell 算力是 hopper 的 68 倍,下一代计划明年推出的 Rubin 算力是 hopper 的900 倍,一年一迭代,远比摩尔定律快的速度,还做了大量的大规模部署的优化,省电、稳定,号称买越多,省越多,赚越多,竞对看起来会很难追上。这些论述还是挺能让人 buyin 的。

Agentic AI

Agent 的相关 session 有接近 200 个,Agent 集合了几个元素:

  1. 概念火,一些涉及 Workflow/RAG 什么的 AI 应用都统一称为 Agent 了,GenAI 在各行业的落地都可以冠以 Agent 的名义,跟以前 H5 那样,不纠结于具体定义,只要有一个统一称呼。
  2. 人群广,Agent 目前主要是在上层的工程架构上,大量的工程师都能理解、参与讨论、建设,不像基础模型训练,多数人难以参与。
  3. 应用广,非研发也能大概听得懂,涵盖了 AI 在各行业的应用这个课题,各行业都会有兴趣了解 Agent 是什么,自己业务上能怎么用。

所以 Agent 相关的 session 大部分都很热门。听完一些的感受:

  1. 多数做企业服务、云的公司都在卷 Agent 的基建和解决方案,像基础设施公司 Fireworks AI、Nebius,数据库公司 Couchbase、datastax,企业服务公司 serviceNow、Dropbox,新兴公司 huggingface、langchain、langflow 等,都来分享推广在 Agent 这事上能提供的能力和服务。
  2. Agent 相关的建设都在刚起步,基本都是在分享概念、工程问题的优化和应用方案,没看到有涉及模型训练去优化 Agent 效果上限的相关分享。Agent 的一些关键课题上一篇文章有提到,基本差不太多。
  3. 也没有讨论 Agent 在工程和模型上的界限,后续端到端的模型进步,能吃掉多少 Agent 能做的事?这两天 4o 的图生成出来后,预计后面才会有更多的讨论。

NVidia AI 基础服务

NVidia 作为领头羊,是希望自己能覆盖 AI 全链路基础设施的,大力在 AI 的每一层都提供了相关框架、服务、能力,这次会议上也有非常多的分享和推广。

其中跟 AI 应用 / Agent 相关的几个基建:

  1. BluePrint:应用蓝图。给了很多 AI 应用场景的 example 工作流(也称为 Agent),例如 PDF 转博客、数字人应用等,提供工作流架构、数据集、源码,可定制,供开发者快速参考和部署。
  2. NIM(NVIDIA Inference Microservices**)**:模型推理。把模型推理封装在 Docker 容器里,可以直接快速部署,对外提供标准化API。也封装了模型在不同 GPU 型号下的优化,提升性能效率。
  3. NeMo(Neural Modules):模型训练。提供了相关工具用于构建、定制、训练 AI 模型,训练后的模型可以通过 NIM 部署。
  4. AgentIQ:开源 Agent 开发套件,支持组合链接不同框架创建的 Agent,提供性能 profiler、评估、UI 界面等工具。

这些基建的声量比较低,国内没怎么见到,不确定海外使用情况怎样。

多个 session 都在推广 NVidia 的 Video Search and Summarization Agent,串联从视频的获取→分割→VLM识别、CV物体识别和跟踪→数据处理存储和RAG召回→用户对话 整个流程,做到可以对视频提供实时分析和报警,也可以自然语言交互查询视频内容,边缘部署,适合用于监控,算是用 NVidia 技术栈做 AI 应用的一个标杆范例。

AIGC

关注了下视频 AIGC 相关的几个 Session

  1. 在好莱坞干了几十年的视觉效果的 Ed Ulbrich 开了个公司 Metaphysic,以前的电影特效制作成本巨大,对人的处理还很难跨过恐怖谷,而基于 AI 技术做特效,用完全不同的技术栈,效果好成本低,是一种颠覆。metaphysic 给娱乐行业提供人脸替换、数字人的服务,看起来是用的 GAN,在人物换脸技术上,GAN 还是更能做到稳定和实时,特别是实时这个点,基于 diffusion 很难做到。基于市场需求,利用已有的不同技术(甚至是上一代技术)深入解决问题,是有空间的。
  2. PixVerse Co-Founder 在一次对话中聊到,视频实时生成的能力差不多要 ready 了,目前 5 秒的视频可以做到5-10秒推理完成,可能会解锁新的人跟视频的交互方式。不确定质量怎样,质量达到一个阈值,以前设想的很多类似 自定义剧情走向 的新玩法新交互有很大空间。
  3. Adobe 和 OpenSora 都来分享了视频生成模型的训练和推理的方案和优化,鉴于已经不是SOTA模型,可参考性不高。TCL 分享了AI电影制作,很惊讶这公司竟然在做这个,更多的是在做链路串联,而不是端到端的视频模型。

其他

  1. OpenAI 只来了两个人给 blackwell 架构站站台,Anthropic 一个人也没来,从这上看,这行业最领先的技术还是很 close,毕竟是核心竞争力,而且很容易被复刻,不像上个时代,大规模并发架构等技术,更重的是实践中解决具体问题,大方案分享了问题不大。(所以 DeepSeek 开源最领先的技术带来的冲击才会那么大。)
  2. DeepSeek 就是 Reasoning Model 的代名词,开源模型的顶流,出镜率极高,老黄的 keynote、各种演讲里都有它的身影,而 llama 通常是作为上一代开源模型与它做对比,只要是提供开源模型部署服务的公司(HuggingFace/Fireworks等),分享里都会对 DeepSeek 极度推崇。
  3. 遇到不少学生来参加,有的来找方向,看看业界前沿在做什么,做学术交流,找合作机会,这个会是挺合适的。清华、中科大、SJSU。最大的问题是实验室没有足够的卡,这领域是必须校企合作,实验室才进行得下去了。
  4. 使用 Nvidia Jetson 做边缘计算也是预期后续空间比较大的方向,设备端部署模型,可以提升实时性和隐私性,多数分享是用在具身智能上,还有一个分享的场景是在货架上实时分析用户行为,更精准推送广告。
  5. 机器人、自动驾驶的 session 也很多,数字孪生是提得比较多的(用 AI 生成仿真环境,用于机器人训练),但现场没看到什么能震惊人的机器人,包括老黄演讲时演示的类 wall-e 机器人,惊艳不够,这一行感觉还早。

总体感受,眼花缭乱,人潮纷杂,在开拓视野以外,大会更多是一个社交场所,推广产品/技术/服务,促进合作,这类大会需要的是多创造一些面对面交流的机会。

花絮

  1. 现场有限量的原价 5080、5090,知道时已经不可能排队买到。
  2. 跟七年前参加 WWDC 在同一个地方,估计一直还是同一个承办公司,午餐还是那么难吃。
  3. 参观 NVidia 工区,老黄作为华裔也是信风水的,新办公楼会模拟依山傍水的设计,风水好。NVidia 搞渲染出身,渲染里三角形是最基本单元,所以办公楼都是三角形元素。办公环境很宽敞,但没啥人,总部居家办公没有限制,很多都不来公司。

LangChain 作者聊 AI Agent 的几个相关课题

2025-03-24 15:58:50

参加 NVIDIA GTC 会,其中一场听了 LangChain 的作者 Harrison Chase的分享《AI Agents in Production Insights and Future Direct》,聊了 Agent 当前遇到的一些问题和他的想法,包括 Planing,UX,Memory,Reliability,Deployment,Multi-Agent,也结合我的理解说说这几个课题。

Planing

任务规划是 Agent 的核心,这个课题是进展比较多的,业界解决得相对比较好,核心是 o1/r1 推理模型的出现和不断增强,让规划能力上了一个台阶,这也是 agent 能起来的基础。

但模型本身目前解决不了所有问题,还需要工程上的一些策略和串联做优化。例如 Tree of Thought 让任务不是以线性一步步执行的形式,而是生成解决问题的多个节点,多角度思考问题,形成树结构的任务,评估节点的价值,在里面寻找最优解。 Reflexion 会有 Evaluator 对各种反馈(工具调用结果/模型输出/用户指令)进行反思,梳理改进方向,也会把反思结果作为知识库经验,指导后续的任务。

这些策略链路是需要有一个工程流程把他们串起来的,这个工程链路的构建也是 Agent 在 Planing 能不能做好的关键因素,langgraph 和众多 Agent 框架服务都持续在做这个事。

UX

Agent 的交互应该是怎样的?

Devin 多窗口,有聊天框发送指令、又能实时看到 Agent 在怎样用浏览器、命令行、编辑器,是不错的交互。

大部分 Agent 会是后台异步运行的模式,可以让它直接跑在后台,在需要人类给出反馈处理的,用类似邮件 inbox 的方式交互,Agent 发邮件给你等待指示,你回复邮件给输入。

相较于交互界面形态,交互的策略可能更关键。Agent 在执行任务过程中,

  1. 用户是否应该能随时中断并提出新的指示?
  2. Agent 应该在什么时候暂停任务等待用户反馈再进行下一步?
  3. 用户指示应该用表单一次性收集,还是一步步收集?

如果做每一步都要用户反馈做指示,那是非常枯燥不好用的,如果完全不需要用户反馈,那做出来的东西可能不符合用户预期的概率高很多。模型应该能做好这里的交互策略,但目前还没看到有特别好的实践。

Memory

长时记忆是个有意思的话题,杨立昆在对话中也有提到,记忆这个课题是值得研究的方向,现在是缺乏突破和讨论的。

现在的 Agent,普遍都只有知识库 RAG 而没有记忆,记忆不是知识库,或者说知识库只是记忆的一种。

记忆应该跟人类一样,模型能记住和学习交互过程中用户给到的信息和偏好,在每次推理过程中发挥作用。

它跟 UX 相关,如果模型能理解记住用户偏好,用户的反馈交互就可以减少。

它也跟 System Prompt 的优化相关,System Prompt 是激活了模型按某个方向去做推理预测,记忆也应该是在模型推理的过程中发挥作用。

简单做的话记忆可以作为 System Prompt 的一部分去影响模型,更彻底的可能应该是能持续内化到模型内,或者以新的模型架构去做这事。

现在的应用场景还没到记忆是必选项的程度,但要做 AGI 或者要 Agent 好用这块必不可少。

Reliability

主要是指 Agent 能不能稳定地解决同一个(或同一类)问题。

Agent 跟之前的软件工程不同,受限于模型输出的不稳定,整个系统的可靠性是远不如传统工程的,用户输入同样的或差不多的需求,agent 不一定每次都能解决问题。

模型输出的,一是会受用户对任务描述的影响,可能描述不准确,可能会有歧义。二是受模型本身不够聪明的影响,近期模型能力越来越好,解决了部分问题,但仍是不稳定。

保持 Agent 输出的稳定性,是一个非常需要持续迭代优化的工程,搭一个 demo 容易,持续优化难。

Agent 节点多,需要能看清每个任务节点的详细情况,有问题时知道问题出在哪里,需要有效果评估的测试能力,也需要框架有能力比较方便地在过程中对模型的输出进行评估实时纠错,提升稳定性,这些配套 langchain 相关生态都提供了,NVidia 这次开源的 AgentIQ 框架也基本涵盖了,还有很多框架服务也在做。

Deployment

Agent 要在线上跑,相关部署基建现在也还没有很完善,它跟传统工程链路还是有一些区别,主要是链路长、耗时长、成本高。提供 Agent 部署的服务应该针对这几个特性做好相关基础设施。

  1. 稳定性:整个 agent 链路很长,每一个环节调用如果成功率是 99%,平均要调用十次接口的 agent 成功率就只有90%,而大模型的接口往往也不稳定,如何保证成功率? 重试策略、排队机制等,这些都是 agent 工程基建应该做的事。
  2. 性能:当前 agent 处于效果大于耗时的阶段,只要效果好,五分钟输出还是十分钟输出都可以接受,但真正规模化应用起来时,性能问题肯定也是重点,整个链路耗时太长,可优化空间会比较大,NVidia 对 agent 的分享也提到了,很多任务不一定要串行做,可以并行化节省整体耗时。
  3. 监控: Agent 线上跑的效果怎样,准确率多高,有没有安全风险,应该有直接可用的相应配套。
  4. 成本:如果 Agent 全程用最好的模型,跑一次十几分钟的任务可能要几美元的成本,前期问题不大,效果优先,粗放式探索,后续真能规模化上线应用,成本这里的优化空间会比较大,用不同的专家小模型处理不同的任务、做好模型 – GPU 卡适配优化推理(NVidia NIM 提供了相关能力),都是可优化的方向。

Multi-Agent

预期后续会有非常多的 Agent 出现,Agent 跟 Agent 之间如果能相互联系,能形成新的智能体,但 Agent 之间 应该怎样通信?

这里的通信不止是把 Agent 当成一个黑盒,给指令 – 输出结果,而是能深入 Agent 内部的通信,上下文共享、中间步骤共享、过程中的协作、用户操作插入等。

目前没有一个标准,各项目都是自己的一套,业界可能需要这样一个标准,能实现把使用不同框架、不同服务上部署的 agent 连接起来。

MCP 是近期在快速发展的标准协议,很有前景,但它只是把工具工具调用标准化了,对 Agent 和 Agent 相关的协作是没有定义的,可能需要另外的协议。

上一篇文章刚好探讨了这个内容,用 Agent as Tool 的方式,把 Agent 当成工具的一种,基于 MCP 去做,好处是架构简单,Agent 可复用性高。

但它只把 Agent 当成黑盒 Tool 去使用,给指令 → 输出结果,Agent 之间更深入的联系是没有的。我们也在尝试,给这个 MCP 子 Agent 输入主 Agent 的上下文,同时这个子 Agent 也可以流式把每步处理过程上下文输出给主 Agent,这样就可以实现 Agent 之间的上下文共享。同时也可以继续做更深入的交互定义,比如子 Agent 与用户反馈交互的流程协议。

目前这些协议都需要自定义,但以 MCP 、 以 Agent as Tool 去定义标准的 Agent 间交互协议,也是可行的,MCP 可以把这套交互协议也定了,可能是 Anthoropic 很好的机会。


上述这些基本是工程上的事情,这次 GTC 很少有人讨论到 Agent 在数据收集/模型调优上的实践,基本是直接使用基础通用模型,但要提升 Agent 的上限,应该是需要专有模型并能支持端到端训练的形态,待探索。

聊聊 Agent 架构 – Single Agent / MCP / Multi-Agent

2025-03-16 13:42:45

近期在业务中尝试落地 Agent,有一个架构设计问题,应该用单 Agent 架构,还是多 Agent 架构?

Single Agent

先来看看单 Agent 架构,在之前的文章里,OpenHands 这里的架构是典型的单 Agent 架构,依赖一个模型,组织多个工具调用,做好 ReAct 和上下文管理,整个过程很简单。

  1. Tools 是一个个函数,定义和调用都是在当前程序里进行。Tools的函数定义会作为 System Prompt 的一部分让 LLM 理解当前可用工具
  2. Memory 分两部分:
    1. 当前 Session 数据流,包括每一步执行了什么,结果是什么,在当前 Session 内存中保存,随时全量输入 LLM,让 LLM 判断下一步应该做什么。
    2. 用户的长期数据、知识库,例如用户在平台的偏好数据、领域内容、多轮对话上下文等,这些内容会从向量数据库召回。
  3. Router 中心化程序调度整个过程,拿用户 Prompt / System Prompt / Memory 输入 LLM,LLM 进行深度思考和给出具体执行的任务,Router 去调用对应的 Action 函数。

这是简单通用的单 Agent 架构,实现 Agent 中 Thought – Plan – Action – Reflection(Thought) 的循环,一个模型负责所有事情。

MCP

上述架构里,Tools 模块有一些小问题:工具函数可维护性和可扩展性不太好,多了后难管理,要加函数得更新主程序,另外得自己定义一个 Function call 规范,对外部的一些会用到的工具服务都需要自己封装一遍。

对这些小问题,这个架构可以有一个优化:Tool 模块从 Agent 剥离出来,用 MCP 协议统一管理和实现。

附:MCP是什么?

MCP 是 Anthropic 24年11月推出的协议,近期 Cursor / windsurf / cline 等一众 AI Coding 产品支持了 MCP 后出圈,众多开源框架也开始支持 MCP,大有统一的趋势。

MCP 的概念很简单,就是统一了工具调用的接口规范,这几张图可以帮助理解:

  1. MCP 统一了各工具能力接入的接口调用定义,原先一个服务(例如slack)要对接多个用户端产品(例如cursor)定义的 Function call 格式,现在服务和客户端统一对接同一种格式就行,两边都只需要实现一次。
  1. MCP Server 独立运行在任意服务器上,也可以有自己独立的数据库信息/资源,不与 Agent 服务器绑定,可复用、易于插拔。

把原先 Tool 几个工具函数调用用 MCP Server 封装,架构变成这样:

跟原先纯 Function call 的区别在于架构上更灵活,包括:

  1. 聚类,对零散的一个个函数可以统一放到一个服务,便于管理。
  2. 解耦调用实际发生在各自 MCP 服务端,而不是 Agent 服务直接去调用,部署扩展工具与 Agent 工程上解耦。
  3. 内聚:MCP Server 本身可以内聚做一些事,包括独立的资源管理、独立上下文等。
  4. 复用:通用协议,Tool 能力便于在多个 Agent 间接入复用,外部生态有较多现成 MCP Server 可直接接入。
  5. 统一:客户端、云端的工具调用,都可以用统一的 MCP 协议去实现。

这个架构似乎已经可以满足大部分场景下对 Agent 的诉求,为什么还需要考虑 Multi-Agent?

Multi-Agent

考虑 Multi-Agent 最主要的问题是上下文过长

如果一个 Agent 能力足够强,它应该能完成需要非常多轮调用完成各种任务,这些任务的制定和执行结果全部塞在一个上下文里,可能会超出当前模型能理解和处理的范围

这时候,计算机工程的典型解决思路就是:分治模块化。把整体 Agent 能力拆分成解决一个个独立复杂任务的子 Agent,让这些 Agent 在它的范围内能有自主思考和自主行动能力。

从 Agent 的组成来说,必不可少的部分包括:

  1. 模型:独立的处理模型,可以跟其他 Agent 不同,称为专家模型。也可以相同,看需要。
  2. 上下文:独立的多轮 ReAct Loop 上下文管理,完成自己特定的任务
  3. System Prompt:对应任务制定特定的 System Prompt

而 Tools 可以不是 Agent 专用的,这个 Agent 需要什么 Tools,就注册什么 Tools。长时记忆/知识库也可以是多个 Agent 共用的。

架构会变成这样:

这样 Plan Agent 只专门制定计划,它需要知道的上下文是其他几个 Agent 能完成什么大的任务,至于他们调了什么工具怎么完成不用管,只需管它要结果,整个任务的上下文就被分出多个部分,每个 Agent 的上下文对另一个 Agent 可以是黑盒。每个 Agent 也可以有自己对应的模型,做独立的训练和 Prompt 调优。

这样是不是一个更优的架构?

  1. 它的好处是解决了上下文过长,模型处理不好的问题。
  2. 坏处也是很明显:整个架构是复杂化了,而效果也不一定好。多个 Agent 需要协同,Plan Agent 能获取的上下文信息变少了,它没有了更细粒度统筹规划整个任务的能力,变成一个偏项目管理的角色协调各方的工作,多人协作带来信息熵增大,组织效率低。

AI 的范式,可能不应该这样分治,可能大模型在对上下文的支持、细节信息的理解上会越来越好,能统筹把握好各项细节,把一个复杂任务完成,而不是像人类社会一样分工协作。这样对大模型来说,有足够的信息量能做规划/决策/反思,也更便于端到端的模型训练。

从号称泄漏的 Manus Prompt 来看,Manus 也没有 Multi Agent,所有能力包括工具函数都在一个上下文中定义,看起来目前也能跑得起足够复杂的任务。

所以如果项目在早期,没有遇到很明显的瓶颈,并不需要用 Multi-Agent 架构,用 Single Agent 简单的架构足够能做好。工程架构越简单,后续基础模型升级带来的增益越大。

基于 MCP 的(伪)Multi-Agent

再探讨下,如果在应用过程中已经发现上下文处理不过来的问题,或者某个任务的内部实现细节对整个任务无影响,或者三方都实现好了,那采用另一种伪 Multi-Agent 架构,也是可以考虑的方案:

例如对接 browser-base 实现更深度的 research 能力,需要多轮打开不同网页、判断资料收集是否完成、整理资料,有自己的 loop 、上下文和模型。但这个完全可以封装在一个 MCP 服务上,自行闭环完成多网页搜索和整理,不需要与原 Agent 流程上有更深入的交互。

跟上面的 Multi-Agent 架构的区别在于,并没有改变原来单 Agent 架构,不会增加架构复杂度。Agent 不需要感知 MCP 调用背后是 Agent 还是一个普通的函数调用,没有区别。

MCP 协议本身也是 SSE 流式输出,对这个背后是 Agent 的 MCP 调用,要输出多少上下文信息给原 Agent,也是可以非常方便地调控。

以上是近期的一些想法,Agent 是新东西,后续实践有认知的更新再分享。

细看 Claude 3.7 两个重要的 Benchmark:SWE-Bench & TAU-Bench

2025-02-27 20:12:04

Claude 3.7 Sonnet 在万众期待中推出了,为什么期待,因为从 Claude 3.5 Sonnet 发布后,一直是AI Coding Agent 领域最好的模型,综合效果没有对手,后面陆续推出的 o1/o3/DeepSeek 都没能撼动,更让人期待 Claude 3.7 Sonnet 在 AI Coding 领域能不能有进一步提升。

Claude 3.7 放出来的 Benchmark 里,有两个是跟 AI Coding Agent 表现强相关的:

  1. Agentic coding,SWE-bench,衡量解决实际软件工程编码问题的能力。
  2. Agentic tool use,TAU-bench,衡量理解用户意图调用工具执行命令的能力。

可以看到 SWE-bench 有显著的提升,问题解决率 49% 提升到最高 70%,TAU-bench 也有不错的绝对值10个点的提升,确实重点提升了 AI Coding Agent 相关能力。

接下来详细看看这两个 Benchmark 究竟测了什么,可以大致知道,目前模型的能力上限大概是怎样。

SWE-bench

SWE-bench 是由普林斯顿大学 NLP 团队开发的项目,23年10月就开始提出,主要是想找到一种方式可以评估大模型解决实际软件工程问题的能力,而不是像之前只衡量算法题的解决能力。当时还是 Claude 2 和 GPT4 的时代,随着 AI Coding 的逐渐火爆,OpenAI 也加入对这个 benchmark 的完善,这个项目也逐渐成为主流。

数据构造

分三步:

  1. 选靠谱的库:选了 12 个流行的 Python 开源库,选择的标准是,热门库,长期维护,有比较多的 Pull Request 修复相关 issue,PR 的管理也很规范,有很好的测试覆盖率。这些库修复 issue 的 PR 就是我们要获取的测试 case,但会对这些 PR 进行一些过滤。
  2. 特性过滤:1)明确 PR 是解决了某个特定问题。2) PR里包含了测试 case,可以很容易从测试 case 上判断代码修改是否有效。这些在运行前就能过滤出来。
  3. 运行时过滤:这些 PR 应用前后,测试用例中要有明确的从不通过到通过的变化,程序跑起来也不会有错误,便于评估结果。

基于上述规则从 github 热门项目上抽取相关的数据,这些数据还可以持续更新,避免模型因为看到过这些数据而“作弊”。

这是抽取的几个流行的 python 库,以及数据集数量:

经过上述步骤抽取构造数据后,得到 SWE-Bench 数据集,后来 OpenAI 对这个数据集再进行人工过滤筛选掉了一些不太好的 case,比如 issue 问题描述不准确、开发环境难搭建难测试等,也对每个挑选的 case 做了精心人工验证,一共500个样本,组成 SWE-bench_Verified 数据集,现在一般测的是这个数据集。

来看看这个数据集具体都由哪些部分组成:

instance_id: 实例ID,通常格式为 repo_owner__name-PR-number

//代码基本信息
repo: 仓库名
base_commit: PR 提交之前的代码 commit id,定位代码基线
version: 用于运行的版本号
environment_setup_commit: commit id,安装运行环境的代码基线
created_at: PR 创建时间

//PR基本信息
problem_statement: PR 对应的 issue 内容,也就是要解决的问题
test_patch: 这个 PR 提交的测试 case patch 代码
FAIL_TO_PASS: 应用修复的 PR 后会通过的测试 case
PASS_TO_PASS: 应用 PR 前和应用后,都应该通过的测试 case
patch: 这个 PR 修复的 patch 代码,相当于标准答案
hints_text: PR 提交之前,github 上对这个 issue 的讨论 comment。可选,如果要上榜单,禁止使用这个数据。

代码信息、问题描述、测试用例,重点是这几个,剩下的都是用于把程序跑起来、验证修复结果用。

测试执行

大体流程见下面这张图,输入 issue 描述和代码库,模型根据输入,输出要修改的代码,最后有个环境运行模型生成修改的代码,跑测试用例,把应用代码之前没跑通的单元测试跑通,这个任务就完成了。

这个过程一个最大的问题是:代码上下文怎么给模型?这几个热门项目代码库平均 43w 行,不可能直接给,需要有个检索的能力。

项目论文中给了两个方法:

  1. 作弊:上述构造数据时,我们有拿到人类修复这个 issue 提交的 PR 对应的 patch,而这个 patch 里修改到的代码文件,就是最重要的代码上下文,可以直接作为代码上下文给到模型。这个接近于标准答案,除了一些需要更多文件上下文才可以解的问题外。这个只用于做实验,或去检测其他的检索方式命中率如何。
  2. 稀疏检索:用 BM25 算法做检索,基于 issue 的描述搜索相关代码,限制长度在1.3万行-5万行。实验看起来,检索长度在2.7w行时,这种检索方法只有 40% 会命中上述 PR 对应的代码文件。

上述两个检索代码的方法,只是论文中做实验的参考,实际在测试 SWE-Bench 时,各模型会有自己的方法,因为检索代码的准确性对成功率影响也很大,所以榜单上很多是 Agent + 模型 的测试结果,而不是单大模型的。

Claude 3.7 跑分的说明里提到:

SWE-bench Verified: Claude 3.7 Sonnet scores 62.3% out of 500 problems using pass@1 with bash/editor tools plus a "thinking tool" for single-attempt patches — no additional test-time compute used. 70.3% score uses internal scoring and custom scaffold on a reduced subset of problems. Scaffold Deepseek R1 results use the "Agentless" framework. OpenAI results from o3-mini system card cover a different subset of problems with a custom compute.

Claude 没有具体说明怎么做检索代码的,官方blog附录里提到在运行环境上是用了极简的方案,只提供了命令和编辑文件的能力,看起来是只把代码仓库目录扔给 LLM,让它自行去做文件搜索。另外有提到 Deepseek 的跑分是基于 Agentless 框架跑的,Agentless 专门介绍说明了如何跑 SWE-Bench,以及具体是怎么做代码检索的。见 Agentless/README_swebench.md

可以看到 SWE-Bench 测试集其实是比较局限,这里面全是 Python 代码,也基本是纯逻辑代码,不涉及 UI 渲染相关,也不涉及其他语言,很多实际的软件工程场景没有覆盖到,所以即使 benchmark 到 100%,也不代表能解决绝大多数工程问题。

不过这事是一步步推进的,SWE-Bench 刚出来时解决率是个位数,这一年多一步步提上来,Claude 3.7 干到了 70%,解决了这个 Bench,还会有更多的更高难度的 Benchmark 等着,SWE-bench Multimodal 就是其中之一,包含了一些 JS UI 渲染相关的 issue 修复 case,Claude 3.5 也只有 12% 左右的解决率,还有很长路要走。

TAU-bench

TAU-bench 又叫 τ-bench,是 Sierra 团队(OpenAI 董事会主席 Bret Taylor 和谷歌前高管 Clay Bavor 联合创立的 AI 初创公司,主要开发 AI Agent 为企业服务)推出的用于评估 AI Agent 在现实世界场景中性能和可靠性的基准测试。

TAU-bench 设计了两个领域场景

  1. Airline(航空场景),模拟用户在航空业务场景下进行航班查询、预订、改签、退票、机场服务等操作,测试大模型利用工具理解用户需求、提供准确信息、遵循业务规则流程、准确进行业务操作的能力。
  2. Retail(零售场景)模拟在零售场景中进行购物咨询、商品推荐、订单修改、退货换货等操作,同样测试在这个场景下用户需求理解能力、准确处理用户订单等相关问题的能力。

这两个场景都包含了多个复杂任务 case,涉及代理与用户的多轮对话,以及代理使用工具获取信息的能力,这些任务可以综合地评估一个 Agent 所需要的 推理、规则遵循、长期记忆、工具调用等能力。为此 TAU-bench 项目也实现了一个完整的 Agent 框架,以执行这个流程。

数据构造

以零售为例,用于测试准备的数据包含以下几个部分:

  1. 数据库:json 格式,模拟电商零售领域一些订单信息、商品属性。
  2. Tools:操作上述数据库的工具函数,包括获取订单/商品信息、修改订单、修改收货地址等。这些 Tools 信息描述会在一开始给 LLM,让 LLM 知道当前有哪些工具可调用,过程中会 LLM 会根据用户意图调用相应工具修改数据库。
  3. 策略:system prompt,写明了模拟的零售场景下一些背景,包括订单状态说明/退换规则/支付方式规则、工具调用规则等。
  4. Tasks:预先设计好的测试数据集,每个测试 case 包含 instruction 和 actions,instruction 写明了这个测试 case 里用户的诉求,actions 里包含基于这个诉求下,应该调用什么工具方法改写数据库达到目的,相当于标准答案,actions 只用于最后的验证,不会在每轮测试中作为输入。

航空领域也是同样的这几类数据,只是处理的内容变成航班、订单、用户信息管理。

测试执行

具体测试 case 执行的流程:用户 instruction + 领域策略 System Prompt + Tool 描述,一起输入 LLM,LLM 循环逐步输出用户对话、助理回复、工具调用,整个流程就是一个通用的 Agent 交互流程,跟上次说到的 OpenHands 的流程差不多,只是这里用户输入也是 LLM 根据 instruction 模拟生成的。

整个多轮对话结束后,模型在这过程中会调用工具修改数据库,同时再跑一遍测试 case 里预定的 action,看对数据库的修改跟模型在这过程中调工具的修改结果是否一致,一致则测试 case 通过,不正确就不通过。

来看一个具体的例子,这是一条测试case,包含对用户诉求描述的 instruction 和标准答案 actions:

{
    "annotator": 0,
    "user_id": "omar_rossi_1241",
    "instruction": "Your user id is omar_rossi_1241. For your upcoming trip from New York to Chicago, you want to change the passenger to yourself, upgrade it to economy class, and have 3 checked bags. You prefer gift card payment. Your birthday is in your user profile so you do not prefer to provide it. You are reactive to the agent and will not say anything that is not asked.",
    "actions": [
        {
            "name": "update_reservation_flights",
            "arguments": {
                "reservation_id": "FQ8APE",
                "cabin": "economy",
                "flights": [
                    {
                        "flight_number": "HAT056",
                        "date": "2024-05-25",
                    },
                    {
                        "flight_number": "HAT138",
                        "date": "2024-05-25",
                    },
                ],
                "payment_id": "gift_card_8190333",
            },
        },
        {
            "name": "update_reservation_passengers",
            "arguments": {
                "reservation_id": "FQ8APE",
                "passengers": [
                    {
                        "first_name": "Omar",
                        "last_name": "Rossi",
                        "dob": "1970-06-06",
                    }
                ],
            },
        },
        {
            "name": "update_reservation_baggages",
            "arguments": {
                "reservation_id": "FQ8APE",
                "total_baggages": 3,
                "nonfree_baggages": 0,
                "payment_id": "gift_card_8190333",
            },
        },
    ],
},

转化后这个case实际跟大模型交互的过程:

{"traj": [
      {
        "role": "system",
        "content": "# Airline Agent Policy\n\nThe current time is 2024-05-15 15:00:00 EST.\n\nAs an airline agent, you can help users book, modify, or cancel flight reservations.\n\n- Before taking any actions that update the booking database (booking, modifying flights, editing baggage, upgrading cabin class, or updating passenger information), you must list the action details and obtain explicit user confirmation (yes) to proceed.\n\n- You should not provide any information, knowledge, or procedures not provided by the user or available tools, or give subjective recommendations or comments.\n\n- You should only make one tool call at a time, and if you make a tool call, you should not respond to the user simultaneously. If you respond to the user, you should not make a tool call at the same time.\n\n- You should deny user requests that are against this policy.\n\n- You should transfer the user to a human agent if and only if the request cannot be handled within the scope of your actions.\n\n## Domain Basic\n\n- Each user has a profile containing user id, email, addresses, date of birth, payment methods, reservation numbers, and membership tier.\n\n- Each reservation has an reservation id, user id, trip type (one way, round trip), flights, passengers, payment methods, created time, baggages, and travel insurance information.\n\n- Each flight has a flight number, an origin, destination, scheduled departure and arrival time (local time), and for each date:\n  - If the status is "available", the flight has not taken off, available seats and prices are listed.\n  - If the status is "delayed" or "on time", the flight has not taken off, cannot be booked.\n  - If the status is "flying", the flight has taken off but not landed, cannot be booked.\n\n## Book flight\n\n- The agent must first obtain the user id, then ask for the trip type, origin, destination.\n\n- Passengers: Each reservation can have at most five passengers. The agent needs to collect the first name, last name, and date of birth for each passenger. All passengers must fly the same flights in the same cabin.\n\n- Payment: each reservation can use at most one travel certificate, at most one credit card, and at most three gift cards. The remaining amount of a travel certificate is not refundable. All payment methods must already be in user profile for safety reasons.\n\n- Checked bag allowance: If the booking user is a regular member, 0 free checked bag for each basic economy passenger, 1 free checked bag for each economy passenger, and 2 free checked bags for each business passenger. If the booking user is a silver member, 1 free checked bag for each basic economy passenger, 2 free checked bag for each economy passenger, and 3 free checked bags for each business passenger. If the booking user is a gold member, 2 free checked bag for each basic economy passenger, 3 free checked bag for each economy passenger, and 3 free checked bags for each business passenger. Each extra baggage is 50 dollars.\n\n- Travel insurance: the agent should ask if the user wants to buy the travel insurance, which is 30 dollars per passenger and enables full refund if the user needs to cancel the flight given health or weather reasons.\n\n## Modify flight\n\n- The agent must first obtain the user id and the reservation id.\n\n- Change flights: Basic economy flights cannot be modified. Other reservations can be modified without changing the origin, destination, and trip type. Some flight segments can be kept, but their prices will not be updated based on the current price. The API does not check these for the agent, so the agent must make sure the rules apply before calling the API!\n\n- Change cabin: all reservations, including basic economy, can change cabin without changing the flights. Cabin changes require the user to pay for the difference between their current cabin and the new cabin class. Cabin class must be the same across all the flights in the same reservation; changing cabin for just one flight segment is not possible.\n\n- Change baggage and insurance: The user can add but not remove checked bags. The user cannot add insurance after initial booking.\n\n- Change passengers: The user can modify passengers but cannot modify the number of passengers. This is something that even a human agent cannot assist with.\n\n- Payment: If the flights are changed, the user needs to provide one gift card or credit card for payment or refund method. The agent should ask for the payment or refund method instead.\n\n## Cancel flight\n\n- The agent must first obtain the user id, the reservation id, and the reason for cancellation (change of plan, airline cancelled flight, or other reasons)\n\n- All reservations can be cancelled within 24 hours of booking, or if the airline cancelled the flight. Otherwise, basic economy or economy flights can be cancelled only if travel insurance is bought and the condition is met, and business flights can always be cancelled. The rules are strict regardless of the membership status. The API does not check these for the agent, so the agent must make sure the rules apply before calling the API!\n\n- The agent can only cancel the whole trip that is not flown. If any of the segments are already used, the agent cannot help and transfer is needed.\n\n- The refund will go to original payment methods in 5 to 7 business days.\n\n## Refund\n\n- If the user is silver/gold member or has travel insurance or flies business, and complains about cancelled flights in a reservation, the agent can offer a certificate as a gesture after confirming the facts, with the amount being $100 times the number of passengers.\n\n- If the user is silver/gold member or has travel insurance or flies business, and complains about delayed flights in a reservation and wants to change or cancel the reservation, the agent can offer a certificate as a gesture after confirming the facts and changing or cancelling the reservation, with the amount being $50 times the number of passengers.\n\n- Do not proactively offer these unless the user complains about the situation and explicitly asks for some compensation. Do not compensate if the user is regular member and has no travel insurance and flies (basic) economy.\n"
      },
      {
        "role": "user",
        "content": "Hi! I'd like to make a few changes to my trip from New York to Chicago. Can you help me with that?"
      },
      {
        "role": "assistant",
        "content": "Of course! I can help you modify your trip. Could you please provide your user ID and the reservation ID for the trip you want to change?",
      },
      {
        "role": "user",
        "content": "Sure! My user ID is omar_rossi_1241. But I'm not sure about the reservation ID at the moment."
      },
      {
        "role": "assistant",
        "content": null,
        "tool_calls": [
          {
            "function": {
              "arguments": "{"user_id":"omar_rossi_1241"}",
              "name": "get_user_details"
            },
            "id": "call_FXi5dyufwOlkHksVgNwVhhVB",
            "type": "function"
          }
        ]
      },
      {
        "role": "assistant",
        "content": "I found your reservations. Could you please confirm which reservation ID you'd like to modify for your trip from New York to Chicago? Here are your reservation IDs:\n\n- UM3OG5\n- 5RJ7UH\n- FQ8APE\n- QKRY03\n\nLet me know which one you'd like to modify!",
      },
      {
        "role": "user",
        "content": "I'm not entirely certain, but I believe it might be the first one, UM3OG5."
      },
      {
        "role": "assistant",
        "content": null,
        "tool_calls": [
          {
            "function": {
              "arguments": "{"reservation_id":"UM3OG5"}",
              "name": "get_reservation_details"
            },
            "id": "call_FApEDaUHdL2hx8FNbu5UCMb8",
            "type": "function"
          }
        ]
      },
      {
        "role": "assistant",
        "content": "The reservation ID "UM3OG5" is for a round trip from Seattle (SEA) to Dallas/Fort Worth (DFW), not from New York to Chicago. \n\nCould you please check if any of the other reservation IDs match your trip from New York to Chicago? Here are the remaining reservation IDs:\n\n- 5RJ7UH\n- FQ8APE\n- QKRY03\n\nLet me know which one you'd like to modify!",
      },
      {
        "role": "user",
        "content": "In that case, I think it might be FQ8APE. Can you check that for me?"
      },
      {
        "role": "assistant",
        "content": null,
        "tool_calls": [
          {
            "function": {
              "arguments": "{"reservation_id":"FQ8APE"}",
              "name": "get_reservation_details"
            },
            "id": "call_W507pQxieFVdAxvqONuYWvN2",
            "type": "function"
          }
        ]
      },
      {
        "content": "The reservation ID "FQ8APE" is for a one-way trip from Newark (EWR) to Chicago (ORD) with a stop in Houston (IAH). Since this reservation is in basic economy, please note that the flights cannot be modified. However, I can assist with other changes like cabin upgrades or baggage additions.\n\nPlease let me know what specific changes you would like to make to this reservation!",
        "role": "assistant"
      },
      {
        "role": "user",
        "content": "Great! I'd like to add three checked bags, change the passenger to myself, and upgrade to economy class, please."
      },
      {
        "role": "assistant",
        "content": "Here are the changes you want to make to your reservation:\n\n1. **Add three checked bags**: \n   - Since you are a gold member and upgrading to economy class, you will have 3 free checked bags, so no additional charges for the bags.\n\n2. **Change the passenger to yourself**:\n   - Current passenger: Ivan Garcia\n   - New passenger: Omar Rossi (DOB: 1970-06-06)\n\n3. **Upgrade to economy class**:\n   - Upgrade from basic economy to economy class.\n\nPlease confirm if you would like to proceed with these changes.",
      },
      {
        "role": "user",
        "content": "Yes, I'd like to proceed with those changes. And please use a gift card for any payment needed."
      },
      {
        "role": "assistant",
        "content": null,
        "tool_calls": [
          {
            "function": {
              "arguments": "{"reservation_id": "FQ8APE", "passengers": [{"first_name": "Omar", "last_name": "Rossi", "dob": "1970-06-06"}]}",
              "name": "update_reservation_passengers"
            },
            "id": "call_jK7xz4ERk3csc9jBfroPobGs",
            "type": "function"
          }
        ]
      },
      {
        "role": "assistant",
        "content": null,
        "tool_calls": [
          {
            "function": {
              "arguments": "{"reservation_id":"FQ8APE","cabin":"economy","flights":[{"origin":"EWR","destination":"IAH","flight_number":"HAT056","date":"2024-05-25"},{"origin":"IAH","destination":"ORD","flight_number":"HAT138","date":"2024-05-25"}],"payment_id":"gift_card_8190333"}",
              "name": "update_reservation_flights"
            },
            "id": "call_zeyT5c2EYzRvfY42X7YOKOng",
            "type": "function"
          }
        ]
      },
      {
        "role": "assistant",
        "content": null,
        "tool_calls": [
          {
            "function": {
              "arguments": "{"reservation_id":"FQ8APE","total_baggages":3,"nonfree_baggages":0,"payment_id":"gift_card_8190333"}",
              "name": "update_reservation_baggages"
            },
            "id": "call_PA1XaKLPX8egjewaxIArCkRc",
            "type": "function"
          }
        ]
      },
      {
        "role": "assistant",
        "content": "Your reservation has been successfully updated with the following changes:\n\n1. **Passenger Change**: The passenger is now Omar Rossi.\n2. **Cabin Upgrade**: Upgraded to economy class.\n3. **Checked Baggage**: Added three checked bags, all free due to your gold membership.\n\nThe payment for the cabin upgrade was processed using your gift card. If you have any more questions or need further assistance, feel free to ask!",
        
      },
      {
        "role": "user",
        "content": "Thank you for helping with the changes! That's all for now. ###STOP###"
      }
    ],
}

在跑的过程中,可以看到大模型可以一步步理解好用户意图,调用get_reservation_details 工具获取信息,最后依次调用 update_reservation_flights、update_reservation_passengers、update_reservation_baggages,修改数据库。

执行完后,会重新加载初始状态的数据库,把 Task 里的 actions 依次执行一遍,对比最终数据库的状态是否一致,一致则说明模型这轮跑下来,调用的工具和修改的数据跟我们预期的一致,测试通过。

这里可以看到每条测试的过程和结果,可以从 Reward=1/0 看这条 case 是否通过,Claude 3.7 在零售领域问题解决率高达81%,但航空领域只有58%,细看下航空领域一些 case 涉及非常多的查询匹配航班信息、金额计算、行李/支付/退换多步操作,难度还是很大的。

Pass^k

这个测试集还定义了另一个指标:多次稳定通过的概率。因为 Agent 真正使用时,比如让它去订机票、处理退换货,如果执行失败会让人有很大的挫败感,所以它的成功率稳定性很重要,这个 benchmark 定义了pass^k 的指标,也就是对一个测试 case 连续执行 k 次,每次都成功,才能算任务成功

可以看到每个模型的稳定性都不是很好,航空领域下,Claude Sonnet 3.5 从 46% 通过率下降到pass^4 的 22.5%,也就是只对 22.5% 的问题,连续测4遍都能成功。

这跟我们目前体感也一致,Agent 还没那么可靠,并不能期望它在复杂的场景、多轮交互中很稳定地理解意图做出正确的行动。Claude 3.7 没有 pass^k 相关的指标,不确定稳定性是否有提升。

最后

上述两个 Benchmark 都是尽量在模拟真实世界的问题场景,算是模拟得比较好的了,但跟真正现实的使用方式和多样性还是有很大差距,分数只能是个参考,能大致知道模型在哪些方面表现还不好,实际在某个场景下好不好用,还得真正上手测试,实际体验上据了解 Claude 3.7 远好于 3.5,这两个 benchmark 的分数提升还不足以反应优化的程度。

DeepSeek R1 是怎么训练出来的?- R1 论文精读

2025-02-10 10:39:11

背景

DeepSeek 里程碑式的爆火,有必要学习下是怎么回事。

大语言模型的发展,之前一直是以预训练为主,虽然性能一直在提升,但主要是小修小补,跨越式的 GPT5 一直出不来。OpenAI 在 24 年 9 月发布的 o1 提出了新的路线:在预训练好的模型上,用强化学习做后训练,能显著提高模型推理能力,其效果在解数学、编码等问题上屠榜。

但 o1 只说了强化学习能让模型学会思维链的方式提升推理能力,其他的发现都没有公布,加上 o1 一直是期货,12月才正式推出,200美元一个月,普通人都用不上,充满神秘。

而 DeepSeek 自主研发了通过强化学习让模型学会思维链提升推理能力,性能逼近 o1,加上之前 DeepSeekV3 在预训练基础模型上的创新,推理成本也显著低于 o1,直接推出全民免费可用媲美 o1 的模型,甚至在一些中文语境下效果显著超过 o1,大众用户一用感受到 NB,业内人士震惊它的创新能力纷纷学习,美国人民恐慌中国 AI 的发展有超越美国引领技术潮流的可能性,结合大国叙事,爱国情怀,各种元素综合下各觉得都会乐此不疲地研究、使用、讨论,引爆全网。

接下来一步步精读下 DeepSeek-R1 的论文 《DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning》了解现象级 R1 模型是怎么做出来的。

R1 论文

DeepSeek R1 是基于预训练好的 DeepSeekV3 基础模型进行的后训练优化,V3 做了很多创新优化,很大降低了预训练模型和模型推理的成本,是 R1 的基础,这里先不讨论 V3,只看 R1 在 V3 基础上做了什么后训练。

在 DeepSeekR1 这篇论文里核心做的三个事:

  1. R1-Zero:首个证明只通过 RL 而不通过 SFT 就能让模型涌现推理能力。
  2. R1:新的强化学习后训练范式。
  3. 蒸馏:蒸馏显著提升小模型能力,小模型有潜力。

我们一个个具体看下这三个事。

R1-Zero

R1-Zero 证明了对已预训练好的模型,不需要经过 SFT,只需要纯粹的 RL,就能让模型涌现 CoT 推理能力。

SFT是监督式微调,也就是准备好一堆标注好的数据,让模型去学习拟合,可以粗略理解为刷题,大量的刷题学习能解决类似的题目;

RL是强化学习,只告诉模型什么是好的什么是不好的,过程中模型自己学习怎么达到目标,可以理解为不靠刷题,自己去理解探索数学和世界的规律,理论上灵活性强,上限更高,还有可能探索出人类未知的能力。

强化学习首次出圈是 AlphaGo,AlphaGo 先学习人类棋谱,再用强化学习自我对弈进化,而随后的 AlphaGo Zero 没有人类棋谱,只定义好围棋奖励规则,模型自己学习怎么下,达到更高的水平。R1-Zero 这命名也是致敬 Alpha-Zero,因为它们非常类似,脱离人类的指导自主发现规律提升智能。

为什么之前没人做到?

  1. 模型能力没达到一定阈值,仅通过强化学习没法涌现。
  2. 强化学习是种方法,过程中用什么算法做价值判定也很大程度影响效果
  3. o1 可能已经做了同样的探索和结果,也可能没有,它闭源不公开,而 DeepSeek 首次探索并公开了。

GRPO vs PPO

R1-Zero 最大的不同,是在强化学习中使用 GRPO 算法代替 PPO。GRPO 算法也是 DeepSeek 团队在 24 年 2 月《DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models》这篇论文提出的,其核心思想可以理解为两个字:内卷

简单来说,PPO 是用一个模型去评估当前输出的收益,模型觉得这个输出好,就更新参数往这方向靠近,类似四六级考试的判定,有个分数线,过了就好,不过就不好;GRPO 是让模型一次性输出一组数据,在这组数据中选得分最高的,让模型逐步靠近得分高的输出,类似高考的内卷选拔,你只要比同龄人好,就是好。

更具体的可以看这张图:

图上的一些概念:

  • Reference Model:预训练的大语言模型
  • Reward Model:奖励模型,给定输入和输出,给出得分。可以是基于神经网络训练的模型,使用人类标注数据做训练;也可以是规则模型,定死一些规则做打分。这里的输入是大语言模型一次完整的输出。
  • Value Model:对模型输出的每一步做一个预测,预测当前状态下后续能得到的收益。这里的输入是大语言模型每一次 token 的输出。
  • Policy Model:我们正在训练的大语言模型。base 是 Reference Model,训练过程中会不断更新参数,得到我们最终需要的模型。
  • GAE:Generalized Advantage Estimation 广义优势估计,评估每一个动作的好坏程度
  • q:输入 question
  • o:输出 output
  • r:模型完整的输出经过 Reward Model 计算后的分数
  • v:模型每一步的输出后的状态,经过 Value Model 计算后的价值估计值,用于评估当前 token 输出的好坏。
  • KL:Kullback-Leibler divergence KL散度,衡量新策略(当前训练中的模型输出的结果)和旧策略(base模型输出的结果)之间的差异,保障训练中策略更新幅度不要过大,保证稳定性。

PPO 用 Reward Model 计算大模型完整输出的奖励分数(r),过程中会跟原始模型的输出做对比(KL),避免偏离太远。大模型每步 token 输出,都会经过 Value Model 判定输出对后续奖励的期望值(v),经过 GAE 函数计算每步动作的好坏程度,更新我们在训练的大模型 Policy Model 参数。

GRPO 去掉了 Value Model,对每个输入(q)输出多个结果o1 o2 o3 …,奖励模型判断这些结果的好坏,从中选出最好的,更新模型参数。对算法和公式更详细的介绍,推荐这两个讲解:(1)(2)

GRPO 去掉 Value Model,带来了一些好处:

  1. 大大降低计算成本,少了价值模型,训练过程的内存占用、计算量、整体效率都更好。
  2. 更适合开放式的问题推理,不受限于价值模型判断的准确性。
  3. 整个流程也更简洁优美了。

Reward Model

从上图可以看到,主要需要设计的只剩 Reward Model,R1-Zero 设计了一个基于规则的 Reward Model,之所以用简单的规则而不是基于神经网络的模型做奖励判定,一个是不需要训练,简化了流程和降低成本,一个是如果用神经网络模型作为奖励判定,如果这个判定模型不够强,可能会让训练的大语言模型钻空子作弊,效果不一定好。

主要是两个简单的规则:

  1. 答案判断,如果是数学问题,会要求按格式要求输出最终答案,对比答案是否正确,对于 LeeCode 上的程序问题,会用编译器去判断能不能跑通对应的 test case。
  2. 格式判断,有没有按照指定的格式输出内容,也就是思维链的内容在<think></think>里,输出的内容在<answer></answer>里。

训练模板

训练时会输入预置 prompt,让模型按格式要求输出。这个模板有意设置得很简单,避免带偏模型,也不会告诉模型要进行反思性推理或者其他推理策略,确保可以观察到模型在强化学习过程中自发去发现怎样的推理方式才是更有效的。

现象&结果

训练过程中两个重要的现象:

1. 输出越来越长:随着训练量的推进,输出的长度稳步加长,推理能力也随之提升。这也验证了 OpenAI o1 里提到的 test-time scaling,也就是更长的推理输出能更好提升模型推理能力,模型在强化学习过程中自主发现了这个规律。

2. Aha moment:训练过程中模型学会了停下来重新评估前面的思考,而且使用拟人的口气进行了反思。强化学习过程中人类没有输入任何类似的反思引导,模型能自主进行这种反思,十分有趣。我理解为,在预训练的模型里,模型已经有这样的反思意识潜力,在训练的某次输出过程中偶然出现,而出现后的结果对复杂推理有帮助,强化学习的机制会持续鼓励这样的输出。

训练的结果,在推理相关的 benchmark上,基本达到 o1 的水平:

R1-Zero 有非常有趣的发现,即不做监督学习,仅对预训练模型进行强化学习这条路是 OK 的,最终的模型有媲美o1的推理能力,但当前这种方式训练出的 R1-Zero 实际对外使用会有些问题,主要是对人不友好,比如输出的内容中英文混杂,猜测是奖励模型并没有做这种人类阅读友好的奖励判定,模型没理由能做好这块。

R1

为了做出一个推理能力强,输出对人类友好,综合能力 OK 的模型,DeepSeek 另外训练了R1模型。

整个流程可以可以看这图,图片来自 这个视频,顺便推荐这个视频的讲解。

这里做了两个阶段的后训练,每个阶段都是先 SFT,再进行 RL,只是数据和目标不同。

一阶段

  1. 用少量的几千个包含了思维链推理的数据,对预训练模型做 SFT。虽然上面说 R1-Zero 不用 SFT 直接用 RL 也可以涌现推理思维链,但有少量精品数据做 SFT,RL 初期就能让模型生成可读性较好的结果,另外可能也能让训练冷启动效率更高,可以看这个视频感受下,在复现过程中,如果没有 SFT 直接 RL,前期要经历比较长的无效训练。
  2. 用跟训练 R1-Zero 同样的步骤,用 coding、数学、科学、逻辑 这些推理密集的数据,对 SFT 后的模型做强化学习。这次强化学习的奖励模型里增加了对语言一致性的奖励,减少模型输出中英夹杂的概率,虽然加了后发现对模型的推理能力会有些下降,但还能接受。

二阶段

  1. 用把上述强化学习后的模型,生成与推理相关的数据,并对这些数据择优,包括去掉语言一致性不行的case、去掉长篇大论的case、对同个问题生成多次并选择最好的一次,拿到质量相对好的60w个推理相关数据,加上另外收集标注的 20w 个跟推理无关,包含写作、事实性回答、翻译等数据,一起对模型再做一次SFT。R1 的中文输出很强,更大可能是跟这 20w 数据的质量高相关。
  2. 把上述 SFT 后的模型,再做一次强化学习。这次强化学习具体没有展开,主要是引导模型安全输出,过滤有害输出,以及再次提升推理能力的一次训练。这次的奖励模型额外再加上了对通用内容的奖励,在文科内容、日常问答上,应该也准备了一些质量比较高的 case 和奖励规则模型。

这几个步骤后,R1 就训练完成了,可以看到这个基于 V3 模型的后训练过程成本是很低的,数据量不大,但效果非常显著,特别是在编码和数学问题上,R1 相对 V3 提升了几个档次。

而这个过程,看起来也是可以 scale 的,可以用这个训好的模型继续多步生成一些case,择优组成新的数据,继续进行 SFT 和强化学习。

这条显著提升模型推理能力的后训练路跑通了,公开解了 o1 一直遮遮掩掩的强化学习路线,也展现了很好的低成本持续 scale up 的潜力。沿着这条路走能 scale 到什么程度还不太清楚,拭目以待。

蒸馏

R1 训完了,最终我们用的就是上述训练出来的模型,但这篇论文还没完,DeepSeek 的同学发现用上述 R1 训练过程中生成的 60w 推理相关的数据,以及 20w 的额外数据去对小模型做 SFT,小模型性能的提升非常明显。

看起来这一步纯粹是用质量更好的数据对小模型做SFT,只是这些数据大部分是 R1 生成的,相当于是蒸馏提取了 R1 的能力精华,拟合了 R1 的能力,这样也能给小模型带来较好的推理能力提升。

从分数看,这些小模型在数学和 coding 上的性能是不错的,如果1.5b在部分场景上能有媲美4o的效果,那端上大模型会迎来一波应用潮。但实际用起来没那么理想,试过 1.5B 模型基本不遵循指令,有些刷分的感觉,但这仅是做了初步的SFT 后的效果,在这基础上对小模型继续进行强化学习,可能整体能力能有提升,也是值得期待的点。

这里论文上还额外做了另一个实验,用类似 R1-Zero 的方法直接对小模型做强化学习,实验效果是相对用蒸馏数据做 SFT 的方式,效果差很多。一个猜测是小模型就是能力有限,直接用强化学习达到顿悟和性能提升,得基于模型本身能力足够才行。

到这里 R1 论文内容结束了,结尾部分提到后续会在多轮对话、json输出、语言混合、提示词问题、写工程代码弱这些问题上提升的展望,解决这些只是时间问题。

复现

这篇论文介绍了 R1 整个算法、训练流程和结果,但最核心的应该是数据,包括用于 R1-Zero 的数据是什么,数据量有多大,生成的 60w 数据具体是什么样的,标注的 20w 文科数据是什么,这是决定模型效果的另一个核心因素,DeepSeek 的中文效果出圈,应该很大程度还是标注的 20w 文科数据质量高,不确定 RL 带来的推理能力提升在文科这块上的帮助有多大。这些数据没有公开,友商要复刻出 DeepSeek 的效果没那么容易。

网上有不少开始复现 R1 和 R1-Zero 的开源项目研究,最大的是 huggingface 的 open-r1,也有学生在 3B 模型上小规模复现 R1-Zero 的开源项目 TinyZero

感受

  1. DeepSeek APP 上线 20天 DAU 超过 2000w,成为历史用户增长最快的 APP,更让人感受到通用 AI chatbot 没有护城河可言,无论是国内的豆包、kimi,还是 ChatGPT、Claude,只要有更好的模型出现,用户瞬间转移不带留恋的。搜索是有技术积累的壁垒的,社交有关系链,内容平台有内容壁垒,基于 chatbot 形态的产品,没看到有什么壁垒,用户数据没有作用,曾以为模型领先就是壁垒,OpenAI 可以凭借领先收高额费用和大部分用户的忠诚,DeepSeek 这波又打破了这种壁垒,领先者无法保证自己一直领先。
  2. DeepSeek 带来中国自信,曾经认为,类似 AICoding 这种全球无差异竞争的产品,国内同学们怎么搞得过海外那些清一色 MIT 天才搞的产品?DeepSeek 的成功叙事给了这些直面竞争产品很大的信心,这种信心下中概股都被疯狂拉动,还是很让人激动的。

500 美元一个月的 Devin 是怎么实现的

2025-01-19 17:58:23

使用

这两天有机会体验了下 Devin,感受到一些小小的震撼。

虽然之前已经用过 cursor 和 windsurf,它们用的模型都一样,理论上能完成的任务和智力是差不多的,但用 Devin 感受还是不太一样,有种 AGI 已经实现了的感觉。

Cursor 和 Devin 核心区别是交互范式,Cursor 是 Copilot,在你工作写代码过程中,实时辅助完成编程任务,而 Devin 是一个员工,交给他复杂任务后不用管它,它主动帮你搞定。可能现在这两者完成的很多任务是一致的,但体验有差异。

我试用的其中一个任务,是扔给它开源项目 JSPatch 的 github 地址,告诉它找个 issue 修一下。它会分解任务逐步执行,包括:

  1. 访问 github 网站,浏览issue列表,随机看几个 issue 详情
  2. 挑了个 block 相关 issue,浏览项目相关文件,寻找与 issue 相关联的代码文件,制定修复计划
  3. 写测试用例 → 修改项目代码尝试修复 issue → 跑用例验证(没 iOS 环境没跑起来) → commit 和提交 PR

这个过程是自动和异步的,它跑在虚拟机里,不需要你提供环境,不需要盯着它,它会自己去调研怎么完成这个任务,做完了会来告诉你(如果用 slack,这个体验过程更顺畅,@它下达任务,任务完成slack回复),这跟给一个员工布置任务,等他做完验收结果的体验很一致。

畅想

现在 Devin 解决问题的能力肯定还有限,真正用下来磕磕碰碰很多任务还是完成不好,现在的模型能力下 Cursor 这种 Copilot 的形态是更实用的,但未来理想的形态肯定是 Devin 这种“员工”形态,因为可以解放注意力,无限扩展同一时间能做的事。

可以想象,这种形态未来的优化速度会很乐观:

  1. 基础模型的思维规划能力还没收敛,从sonet o1 到 o3 可以看到还在快速提升
  2. 即使基础模型能力放缓了,模型在领域上的调优还有很大空间。
  3. 更多工具的接入,也能带来更强大的能力和体验。

以及,模型成本必然比摩尔定律更快速的下降,Devin 会持续用最好的模型最贵的方案,但今天 500美金的效果,一年后成本可能只要5美金就能做到。

Devin 所实现的概念早在 23年初 AutoGPT 就提出,只是当时模型能力还不具备,Claude Sonnet / GPT 4o / GPT o1 这种级别推理能力的模型出现后,才具备可用性,Devin 是实现了这个概念下初步可用的雏形,让人看到这个方向已经初步 ready,剩下的就是持续往这个方向优化和扩展了,Devin 确实称得上是数字员工的开端,设计师agent,交易员agent,数据分析师agent,电商agent,预计会陆续出现。

原理

Devin 是怎么实现的?

有个开源项目 OpenHands(前身 OpenDevin),尝试用开源社区的方式去构建类似 Devin 做的事,虽然能力和效果上不能完全对标 Devin,但可以看个基本雏形。相关论文:https://arxiv.org/abs/2407.16741

文中的这张架构图,可以比较好描述 OpenHands 是怎么做:

三个主要部分:

  1. Event Stream:记录每一步指令和执行结果,Action 是指令,Observation 是指令对应的工具执行的结果,Action 和 Observation 最终都是以纯文本记录结果。
  2. Runtime:程序运行在独立的 docker 容器里,提供一些工具给 Agent 调用执行,当前包括 Python 运行、终端命令行、浏览器
  3. Agent:把 Event Stream 里的所有内容作为上下文,输入到 LLM 推理下一步Action。

还有个关键点没有在图上画出来,为什么把 Event Stream 的所有内容输入到 LLM,LLM 就会按照要求推理出下一步 Action?因为输入到 LLM 的除了 Event Stream 的上下文,还有 Agent 本身的 Prompt,这个 Prompt 描述一些处理原则、当前环境、可用的工具、每个工具的参数、预期输出的格式等,以及还配套了一个示例,指引模型按要求输出。这个 Prompt 本身贴在了文末。

我们跟着图上 Event Stream 的示例,跑一下这个流程:

  1. 用户输入一个任务命令:“Can you create a list of numbers from 1 to 10, and create a web page to display them at port 5000?” 到 Event Stream
  2. 当前 Event Stream 只有这一条命令内容,输入到 Agent,Agent 会拿它跟上述预置 prompt 一起输入给 LLM,LLM 会推理出下一步是调用 Python 去创建 app.py 文件,输出 Action 指令到 Event Stream。
  3. 工具 IPython 对应的 Observation 监听到这个指令,在 Runtime 环境执行了这个命令,输出执行结果app.py created” 记录在 Event Stream 上
  4. Event Stream 接收到 Observation 新的执行结果后,Agent 程序会自动继续把整个 Event Stream 记录连同预置 Prompt 输入给 LLM,推测下一步 Action。推测出来的下一步 Action 是把一大段代码用调用 python 命令的方式写入刚才创建的文件
  5. IPython Observation 接收到这个 Action,在 Runtime 环境执行命令,输出执行结果到 Event Stream。

接下来就是不断的循环:Action 驱动 Observation 用工具做处理 → 处理结果输出到 Event Stream → Event Stream 拿所有前文内容到 LLM 输出下一步 Action → 驱动Observation 用工具做处理…

后面的6-9步用了命令行工具和浏览器工具,流程是一样的。这个循环流程什么时候结束?有一个特殊的 Action 叫 finish,如果一个任务 LLM 认为完成了,就会输出调用 finish Action,程序接收到就退出循环,等用户下一步输入。

整体就是自动循环让 LLM 预测下一步动作 → Agent 程序调用工具执行动作 的过程。补充一些点:

  1. 整个项目不涉及模型训练,纯工程方案,使用通用 LLM 模型,可以配置 Claude/GPT/Deepseek 等,不过可预见的演进是根据用户使用数据去优化模型以达到领域内更好的效果。
  2. 这里没有实现像 Devin 在输出 Action 前先会规划好任务的步骤再一步步执行,但要在上述这个系统加上这个规划能力预计不难。
  3. 项目使用 BrowserGym 去和浏览器交互,Agent 对浏览器的操作和识别是另一个大课题,有单独的 benchmark 多种方案,待调研。
  4. 随着 Event Stream 里链路的不断增加,上下文会越来越长,到一定程度 openhands 会做两种处理,一种是压缩内容,把前面的上下文发给 llm 精练总结,用更简短的内容作为后续的上下文。另一种是让 LLM 对过去的内容进行重要度排序,只选择对预测下一步重要的几个记录作为上下文,具体逻辑在 condenser.py 里。更长的上下文会用向量数据库 ChromeDB 存储。

附:预置prompt

You are OpenHands agent, a helpful AI assistant that can interact with a computer to solve tasks.
<IMPORTANT>
//
* If user provides a path, you should NOT assume it's relative to the current working directory. Instead, you should explore the file system to find the file before working on it.
* When configuring git credentials, use "openhands" as the user.name and "[email protected]" as the user.email by default, unless explicitly instructed otherwise.
* The assistant MUST NOT include comments in the code unless they are necessary to describe non-obvious behavior.
RuntimeInfo(available_hosts={'http://localhost:54090': 54090, 'http://localhost:55602': 55602})
</IMPORTANT>

<RUNTIME_INFORMATION>
The user has access to the following hosts for accessing a web application,
each of which has a corresponding port:
* http://localhost:54090 (port 54090)
* http://localhost:55602 (port 55602)

When starting a web server, use the corresponding ports. You should also
set any options to allow iframes and CORS requests.
</RUNTIME_INFORMATION>
You have access to the following functions:

---- BEGIN FUNCTION #1: execute_bash ----
Description: Execute a bash command in the terminal.
* Long running commands: For commands that may run indefinitely, it should be run in the background and the output should be redirected to a file, e.g. command = `python3 app.py > server.log 2>&1 &`.
* Interactive: If a bash command returns exit code `-1`, this means the process is not yet finished. The assistant must then send a second call to terminal with an empty `command` (which will retrieve any additional logs), or it can send additional text (set `command` to the text) to STDIN of the running process, or it can send command like `C-c` (Ctrl+C) to interrupt the process.

Parameters:
  (1) command (string, required): The bash command to execute. Can be empty string to view additional logs when previous exit code is `-1`. Can be `C-c` (Ctrl+C) to interrupt the currently running process.
---- END FUNCTION #1 ----

---- BEGIN FUNCTION #2: finish ----
Description: Finish the interaction when the task is complete OR if the assistant cannot proceed further with the task.
No parameters are required for this function.
---- END FUNCTION #2 ----

---- BEGIN FUNCTION #3: web_read ----
Description: Read (convert to markdown) content from a webpage. You should prefer using the `web_read` tool over the `browser` tool, but do use the `browser` tool if you need to interact with a webpage (e.g., click a button, fill out a form, etc.).

You may use the `web_read` tool to read content from a webpage, and even search the webpage content using a Google search query (e.g., url=`https://www.google.com/search?q=YOUR_QUERY`).

Parameters:
  (1) url (string, required): The URL of the webpage to read. You can also use a Google search query here (e.g., `https://www.google.com/search?q=YOUR_QUERY`).
---- END FUNCTION #3 ----

---- BEGIN FUNCTION #4: browser ----
Description: Interact with the browser using Python code. Use it ONLY when you need to interact with a webpage.

See the description of "code" parameter for more details.

Multiple actions can be provided at once, but will be executed sequentially without any feedback from the page.
More than 2-3 actions usually leads to failure or unexpected behavior. Example:
fill('a12', 'example with "quotes"')
click('a51')
click('48', button='middle', modifiers=['Shift'])

Parameters:
  (1) code (string, required): The Python code that interacts with the browser.

The following 15 functions are available. Nothing else is supported.

goto(url: str)
    Description: Navigate to a url.
    Examples:
        goto('http://www.example.com')

go_back()
    Description: Navigate to the previous page in history.
    Examples:
        go_back()

go_forward()
    Description: Navigate to the next page in history.
    Examples:
        go_forward()

noop(wait_ms: float = 1000)
    Description: Do nothing, and optionally wait for the given time (in milliseconds).
    You can use this to get the current page content and/or wait for the page to load.
    Examples:
        noop()

        noop(500)

scroll(delta_x: float, delta_y: float)
    Description: Scroll horizontally and vertically. Amounts in pixels, positive for right or down scrolling, negative for left or up scrolling. Dispatches a wheel event.
    Examples:
        scroll(0, 200)

        scroll(-50.2, -100.5)

fill(bid: str, value: str)
    Description: Fill out a form field. It focuses the element and triggers an input event with the entered text. It works for <input>, <textarea> and [contenteditable] elements.
    Examples:
        fill('237', 'example value')

        fill('45', 'multi-line
example')

        fill('a12', 'example with "quotes"')

select_option(bid: str, options: str | list[str])
    Description: Select one or multiple options in a <select> element. You can specify option value or label to select. Multiple options can be selected.
    Examples:
        select_option('a48', 'blue')

        select_option('c48', ['red', 'green', 'blue'])

click(bid: str, button: Literal['left', 'middle', 'right'] = 'left', modifiers: list[typing.Literal['Alt', 'Control', 'ControlOrMeta', 'Meta', 'Shift']] = [])
    Description: Click an element.
    Examples:
        click('a51')

        click('b22', button='right')

        click('48', button='middle', modifiers=['Shift'])

dblclick(bid: str, button: Literal['left', 'middle', 'right'] = 'left', modifiers: list[typing.Literal['Alt', 'Control', 'ControlOrMeta', 'Meta', 'Shift']] = [])
    Description: Double click an element.
    Examples:
        dblclick('12')

        dblclick('ca42', button='right')

        dblclick('178', button='middle', modifiers=['Shift'])

hover(bid: str)
    Description: Hover over an element.
    Examples:
        hover('b8')

press(bid: str, key_comb: str)
    Description: Focus the matching element and press a combination of keys. It accepts the logical key names that are emitted in the keyboardEvent.key property of the keyboard events: Backquote, Minus, Equal, Backslash, Backspace, Tab, Delete, Escape, ArrowDown, End, Enter, Home, Insert, PageDown, PageUp, ArrowRight, ArrowUp, F1 - F12, Digit0 - Digit9, KeyA - KeyZ, etc. You can alternatively specify a single character you'd like to produce such as "a" or "#". Following modification shortcuts are also supported: Shift, Control, Alt, Meta, ShiftLeft, ControlOrMeta. ControlOrMeta resolves to Control on Windows and Linux and to Meta on macOS.
    Examples:
        press('88', 'Backspace')

        press('a26', 'ControlOrMeta+a')

        press('a61', 'Meta+Shift+t')

focus(bid: str)
    Description: Focus the matching element.
    Examples:
        focus('b455')

clear(bid: str)
    Description: Clear the input field.
    Examples:
        clear('996')

drag_and_drop(from_bid: str, to_bid: str)
    Description: Perform a drag & drop. Hover the element that will be dragged. Press left mouse button. Move mouse to the element that will receive the drop. Release left mouse button.
    Examples:
        drag_and_drop('56', '498')

upload_file(bid: str, file: str | list[str])
    Description: Click an element and wait for a "filechooser" event, then select one or multiple input files for upload. Relative file paths are resolved relative to the current working directory. An empty list clears the selected files.
    Examples:
        upload_file('572', '/home/user/my_receipt.pdf')

        upload_file('63', ['/home/bob/Documents/image.jpg', '/home/bob/Documents/file.zip'])

---- END FUNCTION #4 ----

---- BEGIN FUNCTION #5: execute_ipython_cell ----
Description: Run a cell of Python code in an IPython environment.
* The assistant should define variables and import packages before using them.
* The variable defined in the IPython environment will not be available outside the IPython environment (e.g., in terminal).

Parameters:
  (1) code (string, required): The Python code to execute. Supports magic commands like %pip.
---- END FUNCTION #5 ----

---- BEGIN FUNCTION #6: str_replace_editor ----
Description: Custom editing tool for viewing, creating and editing files
* State is persistent across command calls and discussions with the user
* If `path` is a file, `view` displays the result of applying `cat -n`. If `path` is a directory, `view` lists non-hidden files and directories up to 2 levels deep
* The `create` command cannot be used if the specified `path` already exists as a file
* If a `command` generates a long output, it will be truncated and marked with `<response clipped>`
* The `undo_edit` command will revert the last edit made to the file at `path`

Notes for using the `str_replace` command:
* The `old_str` parameter should match EXACTLY one or more consecutive lines from the original file. Be mindful of whitespaces!
* If the `old_str` parameter is not unique in the file, the replacement will not be performed. Make sure to include enough context in `old_str` to make it unique
* The `new_str` parameter should contain the edited lines that should replace the `old_str`

Parameters:
  (1) command (string, required): The commands to run. Allowed options are: `view`, `create`, `str_replace`, `insert`, `undo_edit`.
Allowed values: [`view`, `create`, `str_replace`, `insert`, `undo_edit`]
  (2) path (string, required): Absolute path to file or directory, e.g. `/workspace/file.py` or `/workspace`.
  (3) file_text (string, optional): Required parameter of `create` command, with the content of the file to be created.
  (4) old_str (string, optional): Required parameter of `str_replace` command containing the string in `path` to replace.
  (5) new_str (string, optional): Optional parameter of `str_replace` command containing the new string (if not given, no string will be added). Required parameter of `insert` command containing the string to insert.
  (6) insert_line (integer, optional): Required parameter of `insert` command. The `new_str` will be inserted AFTER the line `insert_line` of `path`.
  (7) view_range (array, optional): Optional parameter of `view` command when `path` points to a file. If none is given, the full file is shown. If provided, the file will be shown in the indicated line number range, e.g. [11, 12] will show lines 11 and 12. Indexing at 1 to start. Setting `[start_line, -1]` shows all lines from `start_line` to the end of the file.
---- END FUNCTION #6 ----


If you choose to call a function ONLY reply in the following format with NO suffix:

<function=example_function_name>
<parameter=example_parameter_1>value_1</parameter>
<parameter=example_parameter_2>
This is the value for the second parameter
that can span
multiple lines
</parameter>
</function>

<IMPORTANT>
Reminder:
- Function calls MUST follow the specified format, start with <function= and end with </function>
- Required parameters MUST be specified
- Only call one function at a time
- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after.
- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls


----------

Here's a running example of how to perform a task with the provided tools.

--------------------- START OF EXAMPLE ---------------------

USER: Create a list of numbers from 1 to 10, and display them in a web page at port 5000.

ASSISTANT: Sure! Let me first check the current directory:
<function=execute_bash>
<parameter=command>
pwd && ls
</parameter>
</function>

USER: EXECUTION RESULT of [execute_bash]:
/workspace
openhands@runtime:~/workspace$

ASSISTANT: There is no `app.py` file in the current directory. Let me create a Python file `app.py`:
<function=str_replace_editor>
<parameter=command>create</parameter>
<parameter=path>/workspace/app.py</parameter>
<parameter=file_text>
from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
    numbers = list(range(1, 11))
    return str(numbers)

if __name__ == '__main__':
    app.run(port=5000)
</parameter>
</function>

USER: EXECUTION RESULT of [str_replace_editor]:
File created successfully at: /workspace/app.py

ASSISTANT: I have created a Python file `app.py` that will display a list of numbers from 1 to 10 when you run it. Let me run the Python file for you:
<function=execute_bash>
<parameter=command>
python3 app.py > server.log 2>&1 &
</parameter>
</function>

USER: EXECUTION RESULT of [execute_bash]:
[1] 121
[1]+  Exit 1                  python3 app.py > server.log 2>&1

ASSISTANT: Looks like the server is running with PID 121 then crashed. Let me check the server log:
<function=execute_bash>
<parameter=command>
cat server.log
</parameter>
</function>

USER: EXECUTION RESULT of [execute_bash]:
Traceback (most recent call last):
  File "/workspace/app.py", line 2, in <module>
    from flask import Flask
ModuleNotFoundError: No module named 'flask'

ASSISTANT: Looks like the server crashed because the `flask` module is not installed. Let me install the `flask` module for you:
<function=execute_bash>
<parameter=command>
pip3 install flask
</parameter>
</function>

USER: EXECUTION RESULT of [execute_bash]:
Defaulting to user installation because normal site-packages is not writeable
Collecting flask
  Using cached flask-3.0.3-py3-none-any.whl (101 kB)
Collecting blinker>=1.6.2
  Using cached blinker-1.7.0-py3-none-any.whl (13 kB)
Collecting Werkzeug>=3.0.0
  Using cached werkzeug-3.0.2-py3-none-any.whl (226 kB)
Collecting click>=8.1.3
  Using cached click-8.1.7-py3-none-any.whl (97 kB)
Collecting itsdangerous>=2.1.2
  Using cached itsdangerous-2.2.0-py3-none-any.whl (16 kB)
Requirement already satisfied: Jinja2>=3.1.2 in /home/openhands/.local/lib/python3.10/site-packages (from flask) (3.1.3)
Requirement already satisfied: MarkupSafe>=2.0 in /home/openhands/.local/lib/python3.10/site-packages (from Jinja2>=3.1.2->flask) (2.1.5)
Installing collected packages: Werkzeug, itsdangerous, click, blinker, flask
Successfully installed Werkzeug-3.0.2 blinker-1.7.0 click-8.1.7 flask-3.0.3 itsdangerous-2.2.0

ASSISTANT: The `flask` module is installed successfully. Let me run the Python file again:
<function=execute_bash>
<parameter=command>
python3 app.py > server.log 2>&1 &
</parameter>
</function>

USER: EXECUTION RESULT of [execute_bash]:
[1] 124

ASSISTANT:
Let me check the server log again:
<function=execute_bash>
<parameter=command>
cat server.log
</parameter>
</function>

USER: EXECUTION RESULT of [execute_bash]:
* Serving Flask app 'app'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5000
Press CTRL+C to quit

ASSISTANT:
The server is running on port 5000 with PID 124. You can access the list of numbers by visiting http://127.0.0.1:5000. If you have any further questions, feel free to ask!

USER: Now kill the server, make it display the numbers in a table format.

ASSISTANT:
Sure! Let me stop the server first:
<function=execute_bash>
<parameter=command>
kill 124
</parameter>
</function>

USER: EXECUTION RESULT of [execute_bash]:
[1]+  Terminated              python3 app.py > server.log 2>&1

ASSISTANT:
Now let me display the numbers in a table format:
<function=str_replace_editor>
<parameter=command>str_replace</parameter>
<parameter=path>/workspace/app.py</parameter>
<parameter=old_str>return str(numbers)</parameter>
<parameter=new_str>return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'</parameter>
</function>

USER: EXECUTION RESULT of [str_replace_editor]:
The file /workspace/app.py has been edited. Here's the result of running `cat -n` on a snippet of /workspace/app.py:
     3
     4  @app.route('/')
     5  def index():
     6      numbers = list(range(1, 11))
     7      return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'
     8
     9  if __name__ == '__main__':
    10      app.run(port=5000)
Review the changes and make sure they are as expected. Edit the file again if necessary.

ASSISTANT:
Running the updated file:
<function=execute_bash>
<parameter=command>
<parameter=command>
python3 app.py > server.log 2>&1 &
</parameter>
</function>

USER: EXECUTION RESULT of [execute_bash]:
[1] 126

ASSISTANT:
The server is running on port 5000 with PID 126. You can access the list of numbers in a table format by visiting http://127.0.0.1:5000. Let me know if you have any further requests!
<function=finish>
</function>

--------------------- END OF EXAMPLE ---------------------

Do NOT assume the environment is the same as in the example above.

附:任务 Prompt示例

以下是真实执行一个任务时,循环添加输入 LLM 的 Prompt case:

--------------------- NEW TASK DESCRIPTION ---------------------
创建一个可以调用deepseek模型的程序,并运行测试确保可用
--------------------- END OF NEW TASK DESCRIPTION ---------------------

PLEASE follow the format strictly! PLEASE EMIT ONE AND ONLY ONE FUNCTION CALL PER MESSAGE.


----------

Let me start by checking the current directory and listing its contents:

<function=execute_bash>
<parameter=command>pwd && ls</parameter>
</function>

----------

EXECUTION RESULT of [execute_bash]:
/workspace
[Python Interpreter: /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/bin/python]
[Command finished with exit code 0]

----------

I see we are in the `/workspace` directory. Let's create a Python file that can call the DeepSeek model. First, I'll create a new file called `deepseek_app.py`:

<function=str_replace_editor>
<parameter=command>create</parameter>
<parameter=path>/workspace/deepseek_app.py</parameter>
<parameter=file_text>
from deepseek import DeepSeekModel

def call_deepseek_model(input_text):
    model = DeepSeekModel()
    response = model.generate(input_text)
    return response

if __name__ == "__main__":
    test_input = "Hello, DeepSeek!"
    result = call_deepseek_model(test_input)
    print("Model Response:", result)
</parameter>
</function>

----------

EXECUTION RESULT of [str_replace_editor]:
File created successfully at: /workspace/deepseek_app.py

----------

Now, let's check if the `deepseek` module is installed. If not, we'll need to install it:

<function=execute_bash>
<parameter=command>pip3 show deepseek</parameter>
</function>

----------

EXECUTION RESULT of [execute_bash]:
WARNING: Package(s) not found: deepseek
[Python Interpreter: /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/bin/python]
[Command finished with exit code 1]

----------

The `deepseek` module is not installed. Let me install it using pip:

<function=execute_bash>
<parameter=command>pip3 install deepseek</parameter>
</function>

----------

EXECUTION RESULT of [execute_bash]:
Collecting deepseek
  Downloading deepseek-1.0.0-py3-none-any.whl.metadata (3.9 kB)
Requirement already satisfied: requests in /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/lib/python3.12/site-packages (from deepseek) (2.32.3)
Requirement already satisfied: charset-normalizer<4,>=2 in /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/lib/python3.12/site-packages (from requests->deepseek) (3.4.1)
Requirement already satisfied: idna<4,>=2.5 in /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/lib/python3.12/site-packages (from requests->deepseek) (3.10)
Requirement already satisfied: urllib3<3,>=1.21.1 in /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/lib/python3.12/site-packages (from requests->deepseek) (2.3.0)
Requirement already satisfied: certifi>=2017.4.17 in /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/lib/python3.12/site-packages (from requests->deepseek) (2024.12.14)
Downloading deepseek-1.0.0-py3-none-any.whl (4.5 kB)
Installing collected packages: deepseek
Successfully installed deepseek-1.0.0
[Python Interpreter: /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/bin/python]
[Command finished with exit code 0]

----------

The `deepseek` module is now installed. Let's run the `deepseek_app.py` program to test if it works:

<function=execute_bash>
<parameter=command>python3 deepseek_app.py</parameter>
</function>

----------

EXECUTION RESULT of [execute_bash]:
Traceback (most recent call last):
  File "/workspace/deepseek_app.py", line 1, in <module>
    from deepseek import DeepSeekModel
ImportError: cannot import name 'DeepSeekModel' from 'deepseek' (/openhands/poetry/openhands-ai-5O4_aCHf-py3.12/lib/python3.12/site-packages/deepseek/__init__.py)
[Python Interpreter: /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/bin/python]
[Command finished with exit code 1]

----------

It seems the `DeepSeekModel` class is not available in the `deepseek` module. Let me check the documentation or available classes in the `deepseek` module:

<function=execute_bash>
<parameter=command>python3 -c "import deepseek; print(dir(deepseek))"</parameter>
</function>

----------

EXECUTION RESULT of [execute_bash]:
['DeepSeekAPI', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', 'api', 'const']
[Python Interpreter: /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/bin/python]
[Command finished with exit code 0]

----------

The `deepseek` module provides a `DeepSeekAPI` class instead of `DeepSeekModel`. Let me update the `deepseek_app.py` file to use the correct class:

<function=str_replace_editor>
<parameter=command>str_replace</parameter>
<parameter=path>/workspace/deepseek_app.py</parameter>
<parameter=old_str>
from deepseek import DeepSeekModel

def call_deepseek_model(input_text):
    model = DeepSeekModel()
    response = model.generate(input_text)
    return response
</parameter>
<parameter=new_str>
from deepseek import DeepSeekAPI

def call_deepseek_model(input_text):
    api = DeepSeekAPI()
    response = api.generate(input_text)
    return response
</parameter>
</function>

----------

EXECUTION RESULT of [str_replace_editor]:
The file /workspace/deepseek_app.py has been edited. Here's the result of running `cat -n` on a snippet of /workspace/deepseek_app.py:
     1        from deepseek import DeepSeekAPI
     2        
     3        def call_deepseek_model(input_text):
     4            api = DeepSeekAPI()
     5            response = api.generate(input_text)
     6            return response
     7        
     8        if __name__ == "__main__":
     9            test_input = "Hello, DeepSeek!"
    10            result = call_deepseek_model(test_input)
Review the changes and make sure they are as expected. Edit the file again if necessary.

----------

Now that the file is updated, let's run the program again to test if it works:

<function=execute_bash>
<parameter=command>python3 deepseek_app.py</parameter>
</function>

----------

EXECUTION RESULT of [execute_bash]:
Traceback (most recent call last):
  File "/workspace/deepseek_app.py", line 10, in <module>
    result = call_deepseek_model(test_input)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspace/deepseek_app.py", line 4, in call_deepseek_model
    api = DeepSeekAPI()
          ^^^^^^^^^^^^^
  File "/openhands/poetry/openhands-ai-5O4_aCHf-py3.12/lib/python3.12/site-packages/deepseek/api.py", line 9, in __init__
    raise ValueError("DEEPSEEK_API_KEY is missing")
ValueError: DEEPSEEK_API_KEY is missing
[Python Interpreter: /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/bin/python]
[Command finished with exit code 1]

----------

The program requires an API key to use the `DeepSeekAPI`. Let me update the program to include the API key as an environment variable. First, I'll modify the `deepseek_app.py` file:

<function=str_replace_editor>
<parameter=command>str_replace</parameter>
<parameter=path>/workspace/deepseek_app.py</parameter>
<parameter=old_str>
from deepseek import DeepSeekAPI

def call_deepseek_model(input_text):
    api = DeepSeekAPI()
    response = api.generate(input_text)
    return response
</parameter>
<parameter=new_str>
import os
from deepseek import DeepSeekAPI

def call_deepseek_model(input_text):
    api_key = os.getenv("DEEPSEEK_API_KEY")
    if not api_key:
        raise ValueError("Please set the DEEPSEEK_API_KEY environment variable.")
    api = DeepSeekAPI(api_key=api_key)
    response = api.generate(input_text)
    return response
</parameter>
</function>

----------

EXECUTION RESULT of [str_replace_editor]:
The file /workspace/deepseek_app.py has been edited. Here's the result of running `cat -n` on a snippet of /workspace/deepseek_app.py:
     1        import os
     2        from deepseek import DeepSeekAPI
     3        
     4        def call_deepseek_model(input_text):
     5            api_key = os.getenv("DEEPSEEK_API_KEY")
     6            if not api_key:
     7                raise ValueError("Please set the DEEPSEEK_API_KEY environment variable.")
     8            api = DeepSeekAPI(api_key=api_key)
     9            response = api.generate(input_text)
    10            return response
    11        
    12        if __name__ == "__main__":
    13            test_input = "Hello, DeepSeek!"
    14            result = call_deepseek_model(test_input)
Review the changes and make sure they are as expected. Edit the file again if necessary.

----------

Now, let's set the `DEEPSEEK_API_KEY` environment variable and run the program again. Please provide the API key for DeepSeek, and I will set it up for you. If you don't have one, you may need to sign up for an API key from DeepSeek's official website.

<function=execute_bash>
<parameter=command>export DEEPSEEK_API_KEY=your_api_key_here && python3 deepseek_app.py</parameter>
</function>

----------

EXECUTION RESULT of [execute_bash]:
Traceback (most recent call last):
  File "/workspace/deepseek_app.py", line 14, in <module>
    result = call_deepseek_model(test_input)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspace/deepseek_app.py", line 9, in call_deepseek_model
    response = api.generate(input_text)
               ^^^^^^^^^^^^
AttributeError: 'DeepSeekAPI' object has no attribute 'generate'
[Python Interpreter: /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/bin/python]
[Command finished with exit code 1]

----------

It seems the `DeepSeekAPI` class does not have a `generate` method. Let me check the available methods in the `DeepSeekAPI` class:

<function=execute_bash>
<parameter=command>python3 -c "from deepseek import DeepSeekAPI; print(dir(DeepSeekAPI))"</parameter>
</function>

----------

EXECUTION RESULT of [execute_bash]:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_post_request', 'chat_completion', 'completion_impl', 'fim_completion', 'get_models', 'user_balance']
[Python Interpreter: /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/bin/python]
[Command finished with exit code 0]

----------

The `DeepSeekAPI` class provides methods like `chat_completion`, `fim_completion`, and `get_models`. Let's update the `deepseek_app.py` file to use the `chat_completion` method instead of `generate`:

<function=str_replace_editor>
<parameter=command>str_replace</parameter>
<parameter=path>/workspace/deepseek_app.py</parameter>
<parameter=old_str>
api = DeepSeekAPI(api_key=api_key)
    response = api.generate(input_text)
    return response
</parameter>
<parameter=new_str>
api = DeepSeekAPI(api_key=api_key)
    response = api.chat_completion(input_text)
    return response
</parameter>
</function>

----------

EXECUTION RESULT of [str_replace_editor]:
The file /workspace/deepseek_app.py has been edited. Here's the result of running `cat -n` on a snippet of /workspace/deepseek_app.py:
     4        def call_deepseek_model(input_text):
     5            api_key = os.getenv("DEEPSEEK_API_KEY")
     6            if not api_key:
     7                raise ValueError("Please set the DEEPSEEK_API_KEY environment variable.")
     8            api = DeepSeekAPI(api_key=api_key)
     9            response = api.chat_completion(input_text)
    10            return response
    11        
    12        if __name__ == "__main__":
    13            test_input = "Hello, DeepSeek!"
    14            result = call_deepseek_model(test_input)
Review the changes and make sure they are as expected. Edit the file again if necessary.

----------

Now, let's run the program again with the updated method. Please provide the `DEEPSEEK_API_KEY` environment variable:

<function=execute_bash>
<parameter=command>export DEEPSEEK_API_KEY=your_api_key_here && python3 deepseek_app.py</parameter>
</function>

----------

EXECUTION RESULT of [execute_bash]:
Traceback (most recent call last):
  File "/workspace/deepseek_app.py", line 14, in <module>
    result = call_deepseek_model(test_input)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspace/deepseek_app.py", line 9, in call_deepseek_model
    response = api.chat_completion(input_text)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openhands/poetry/openhands-ai-5O4_aCHf-py3.12/lib/python3.12/site-packages/deepseek/api.py", line 62, in chat_completion
    response = self._post_request(API_CHAT_COM, payload, stream)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openhands/poetry/openhands-ai-5O4_aCHf-py3.12/lib/python3.12/site-packages/deepseek/api.py", line 25, in _post_request
    raise Exception(f"HTTP Error {response.status_code}: {response.text}")
Exception: HTTP Error 401: {"error":{"message":"Authentication Fails (no such user)","type":"authentication_error","param":null,"code":"invalid_request_error"}}
[Python Interpreter: /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/bin/python]
[Command finished with exit code 1]

----------

The error indicates that the provided API key is invalid or does not correspond to a valid user. To proceed, you will need to:

1. Obtain a valid API key from DeepSeek's official platform.
2. Ensure the API key is correctly set in the environment.

Once you have a valid API key, you can set it and run the program again. Let me know if you need further assistance!

----------

key: *** ///在这里输入了key,隐藏

----------

Thank you for providing the API key. Let's set the environment variable and run the program again:

<function=execute_bash>
<parameter=command>export DEEPSEEK_API_KEY=sk-4a727097b1ef4e61a3444ec96e1e0db0 && python3 deepseek_app.py</parameter>
</function>

----------

EXECUTION RESULT of [execute_bash]:
Model Response: Hello! How can I assist you today? 😊
[Python Interpreter: /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/bin/python]
[Command finished with exit code 0]