标签归档:AI

SaaS 已死?AI 当立?

在 AI 编程逛飙的年份,到当前的阶段,能感觉到速度在加快,形式也在不断的迭代,写代码这个事情也变得门槛很低。

X 上有人在讲「AI 让软件开发成本接近零,所以 SaaS 价值也接近零」

美股前段时间也因此大跌了一波,但当前这个价值为零的逻辑还是不成立的。

这里有一个概念混淆:构建软件的成本 vs 拥有软件的成本。AI 主要压低前者,后者还在,甚至在很多公司里变得更贵。

在各种自媒体、AI 编程培训或者博眼球的报道中,「一个下午做出 Linear 替代」「一个周末写完 Stripe 替代」这种话,我不觉得完全是吹牛。用 Claude Code 这类工具,把界面、CRUD、简单流程、甚至一些边角的自动化都拼出来,确实快。

但在做出来的那一刻,资产没增加多少,负债突然多了。尤其是碰钱、碰身份、碰合规、碰客户数据的时候。

以做了一个 Stripe 为例,我把 Vibe Coding 一个周末后,「现在拥有了什么」用更工程的语言翻译一下:

  • 拥有了 规则持续变化 的税务与开票适配:欧盟 VAT、各国电子发票、免税/退税、税率变更、发票作废红冲。
  • 拥有了 审计与认证的对话成本:PCI DSS、SOC 2、ISO 27001、渗透测试报告、供应商安全评估问卷。企业客户要的往往不是「你写得对不对」,是「谁在对这件事负责」。
  • 拥有了 支付与账务的边界条件:拒付、部分退款、重复扣款、汇率、四舍五入、账期、对账差异、资金在途、延迟入账。
  • 拥有了 全球化的坑:货币重估、货币小数位变化、地区性监管、某个国家突然要求强制 3DS、某个渠道突然不支持某种卡组织。
  • 拥有了 数据与权限的事故半径:某个字段脱敏没做好、某个导出接口忘了做权限校验、某个后台操作没有审计日志。
  • 拥有了 7×24 的值班现实:系统降级策略、容量、限流、重试风暴、第三方故障兜底、SLA 与赔付条款。

这些事大概率不是「可能发生」。按行业经验只是早晚问题。自从拥有系统开始,就等于自己签了那张无限期维护合同。

回想一个问题,SaaS 是什么?软件即服务,核心是服务,根本就没有提代码。

很多团队在内部系统上很执着,原因很直接:控制感强、改需求快、看起来省钱。上线半年以后,气氛通常变得不太好:需求排队、线上出过几次事故、业务抱怨响应慢、研发觉得自己在打杂。

这时候再看 SaaS,价值就很清晰了:SaaS 的核心商品是「运营表面积」的转移

代码只是其中一层,除了代码还有:

  • 基础设施与部署:多区域、容灾、备份恢复演练、变更管理。
  • 安全:漏洞扫描、依赖升级、密钥轮换、权限最小化、WAF、DDoS。
  • 合规:隐私条款、数据驻留、审计证据、流程制度落地。
  • 可靠性:监控告警、事故复盘、容量规划、灰度发布、回滚策略。
  • 支持与客户成功:工单、排障、培训、文档、FAQ、升级沟通。

当我们把软件「买回来」自己跑,这些都要自己补齐。很多公司低估的就是这一块。这也是为什么很多公司在 AI 赋能后,「运营表面积」的转移速度要慢于「代码」的转移速度。除非这些对公司不重要,如果这些都不重要, 这个系统可能也不重要了。

从 SaaS 的生命周期来看,AI 提升的是「0 到 1」,然而从成本的角度看,最贵的是「1 到 ∞」

这件事可以拆成两条曲线:

  • vibe coded 工具:初期成本很低,后期成本增长很快。越多人用、越多数据、越多流程依赖,它的「改动风险」和「维护面」指数上升。
  • 成熟 SaaS:初期采购成本看着不低,后期增长更接近线性。因为供应商把大量共性维护摊薄到所有客户身上。

AI 把 0→1 压得更便宜,直觉上会让人误判「买 SaaS 更不划算」。实际情况常常相反:AI 让 1→∞ 更贵了,因为我们会更频繁地改、更大胆地接更多业务进来,系统的负债增长速度被我们自己加速了。

这个成本不仅仅是 SaaS 软件的。

AI 让一个工程师单位时间产出更高,但单位工程小时的机会成本同步上升

至少当前的认知是这样的,可能到终极形态,完成不用人介入的时候,这个机会成本也会消失掉。

那是另一个话题了。

所以我对「AI 让 SaaS 价值归零」的判断是反的:AI 越强,成熟 SaaS 越值钱,前提是它真的把服务做扎实,真的帮我们把运营表面积吃掉。

SaaS 本质上还是一种服务。

如果 SaaS 只是「界面更好看的 CRUD」,那这样的 SaaS 确实会死掉一批。

这些 SaaS 太薄了。

在 AI 时代要想活得更好,SaaS 通常需要如下的一些特征:

  • 规则密度高,正确性要求高
  • 合规与安全负担重
  • 生态变化快,需要持续跟进
  • 故障代价高,客户不想背锅
  • 接口与集成复杂,长期维护吃人

Stripe、WorkOS、Cloudflare 这类产品的共同点很明显:它们难点不在「写出来」,难点在「长期把它跑对」。正确性要靠无数细碎决策堆出来,运营水位要常年拉满。

AI 始终会改写 SaaS,整体逻辑会有一些变化。这里的 AI 改写过程,肯定不是「加一个聊天框」「做一个总结」「做一个生成报表」。

我觉得至少有三个点在当前阶段能快速跟进:

  1. 意图驱动,SaaS 的用户最终都是想通过 SaaS 完成工作,以前靠 UI 引导的,现在可以让用户意图表达,然后把意图落为可审计的操作序列,不管是用 MCP,还是 SKILLS;
  2. 更关注结果,传统 SaaS 交付的是工具,客户负责把工具嵌进流程。AI 让厂商有机会把流程吃进去,直接交付结果,比如「自动完成对账」「自动完成入职」「自动完成工单分流」。关键点在「责任」
  3. 定制方式的迭代,企业客户永远会提定制。以前定制意味着项目制和人力黑洞;AI 让「生成」变得便宜,但不要把生成等同于可维护,可以允许客户用自然语言提出规则,系统把规则编译成可测试、可审计的约束,每次变更都能跑回归校验,出问题能定位到规则版本与变更人。

我们常听到对于自研和购买 SaaS 的一个判断逻辑是:「核心业务自研,非核心买 SaaS」。

这句话太粗,没有啥指导意义。

如下一个判断清单,可以做为决策的一些依据:

适合买 SaaS 的场景

  • 领域合规重:支付、税务、身份、隐私、审计
  • 失败代价高:一出错就上新闻、上法务、上客户群
  • 生态变化快:标准常变、监管常变、攻击手法常变
  • 需要对外背书:企业客户会问「谁负责」「有没有认证」「有没有 SLA」

这种场景自研的隐性成本巨大。AI 再强,也只是让我们更快地把「维护合同」签在自己身上。

适合自研的场景

  • 强差异化:流程就是你的竞争力,外部产品很难贴合
  • 业务规则变化快,且只对内部负责:错了能快速纠正,不会引发合规事故
  • 生命周期短:一个季度就会重构或下线的东西
  • 数据高度敏感,且已经有成熟的数据治理与安全团队

这里自研的价值很实在:迭代速度、贴合度、数据控制。

AI 时代的「自研陷阱」会更隐蔽

以前自研失败,多数死在「做不出来」。现在会死在「做出来以后一路堆债」。

AI 会在早期持续正反馈:

  • 功能做得快
  • Demo 好看
  • 业务觉得爽
  • 老板觉得省钱

债务也在同期累积:

  • 没有威胁建模
  • 没有权限与审计体系
  • 没有数据分级与脱敏
  • 没有灾备演练
  • 没有 SLA 与值班机制
  • 没有供应链安全策略(依赖库、镜像、密钥)

等到系统进入关键路径,会发现自己已经没有退路。再想补课,代价是「停业务」或者「带病重构」。

这也是我反复强调「拥有软件是负债」的原因。负债不会因为 AI 变聪明就消失,它只会增长得更快。

AI 把「做一个能用的软件」变成了常态,把「把软件长期跑对」推成了门槛。薄 SaaS 会被挤压,真正提供服务、背负责任、把运营表面积吃掉的 SaaS,会更值钱。

以上。

AI Agent 核心策略:如何判断 Agent 应该停止

简单来讲,AI Agent 实现的的大逻辑就是一个大的循环 + 获取上下文 + 不停的 LLM 调用 + 工具的调用。

那么一个关键问题就出现了:这个循环什么时候应该停止?如果处理不当,Agent 可能会陷入无限循环,浪费计算资源,或者过早停止而无法完成任务。本文将深入探讨 AI Agent 停止策略的核心设计思路。

常用停止策略

AI Agent 停止策略无外乎以下几种情况:

1. 硬性限制

最简单粗暴的方法:

  • 最大步数限制(比如最多循环 30 次)
  • 执行时间限制(比如最多跑 5 分钟)
  • API 调用次数限制(比如最多调 100 次)
  • API 调用 Token 数限制

这种方法简单有效,但用户体验很差。经常出现任务做到一半被强制停止的情况。

2. 任务完成检测

让 LLM 判断任务是否完成:

# 每次循环后问 LLM
response = llm.ask("任务是否已经完成?")
if response == "是":
    stop()

3. 显式停止信号

给 Agent 一个专门的”停止”工具:

tools = [
    "search",
    "calculate", 
    "terminate"  # 专门用来停止
]

当 Agent 调用 terminate 工具时就停止。这个方法不错,但需要在 prompt 里教会 Agent 什么时候该调用它。

4. 循环检测

检测 Agent 是否在做重复的事:

  • 连续多次调用同一个工具
  • 动作序列出现循环模式(A→B→A→B…)
  • 输出内容高度相似

5. 错误累积

连续失败多次就放弃:

if consecutive_errors > 3:
    stop("连续失败太多次")

6. 用户中断

让用户能随时喊停。

下面我们以 OpenManus 和 Gemini CLI 的源码来看一下他们是怎么做的。

OpenManus 的停止逻辑

OpenManus 的停止机制设计得比较完整,它用了一个多层防护的思路。

核心:terminate 工具

OpenManus 给每个 Agent 都配了一个 terminate 工具:

class Terminate(BaseTool):
    name: str = "terminate"
    description = """当请求已满足或无法继续时终止交互。
    完成所有任务后,调用此工具结束工作。"""
    
    async def execute(self, status: str) -> str:
        return f"交互已完成,状态:{status}"
        
以上为示例,原始代码:
from app.tool.base import BaseTool


_TERMINATE_DESCRIPTION = """Terminate the interaction when the request is met OR if the assistant cannot proceed further with the task.
When you have finished all the tasks, call this tool to end the work."""


class Terminate(BaseTool):
    name: str = "terminate"
    description: str = _TERMINATE_DESCRIPTION
    parameters: dict = {
        "type""object",
        "properties": {
            "status": {
                "type""string",
                "description""The finish status of the interaction.",
                "enum": ["success""failure"],
            }
        },
        "required": ["status"],
    }

    async def execute(self, status: str) -> str:
        """Finish the current execution"""
        return f"The interaction has been completed with status: {status}"

OpenManus 使用的是方案 3,把「何时停止」的决策权交给了 LLM。prompt 里会明确告诉 Agent:任务完成了就调用 terminate。

状态机管理

OpenManus 用状态机来管理 Agent 的生命周期:

class AgentState(Enum):
    IDLE = "idle"
    RUNNING = "running"  
    FINISHED = "finished"
    ERROR = "error"

当检测到特殊工具(如 terminate)被调用时,会触发状态转换:

async def _handle_special_tool(self, name: str, result: Any):
    if name.lower() == "terminate":
        self.state = AgentState.FINISHED
        logger.info(" 任务完成!")

步数限制

不同类型的 Agent 有不同的步数上限:

# ToolCallAgent: 30 步
# SWEAgent: 20 步
# PlanningFlow: 可配置

while self.current_step < self.max_steps and self.state != AgentState.FINISHED:
    self.current_step += 1
    await self.step()

if self.current_step >= self.max_steps:
    results.append(f"达到最大步数限制 ({self.max_steps})")

这是一个保底机制,防止 Agent 无限运行。

卡死检测

OpenManus 还会检测 Agent 是否卡住了:

def is_stuck(self) -> bool:
    # 检查是否有重复的 assistant 消息
    # 如果最近的回复都一样,说明卡住了
    recent_messages = self.get_recent_assistant_messages()
    if len(set(recent_messages)) == 1:
        return True
    return False

Planning Agent 的结束逻辑

1. 计划完成的判断机制

PlanningFlow 的结束判断并不是简单检查所有步骤是否完成:

# 在主执行循环中
while True:
    # 获取当前需要执行的步骤
    self.current_step_index, step_info = await self._get_current_step_info()
    
    # 如果没有更多活跃步骤,则结束计划
    if self.current_step_index is None:
        result += await self._finalize_plan()
        break

2. 步骤状态检查逻辑

_get_current_step_info() 方法负责判断是否还有未完成的步骤:

# 查找第一个非完成状态的步骤
for i, step in enumerate(steps):
    if i >= len(step_statuses):
        status = PlanStepStatus.NOT_STARTED.value
    else:
        status = step_statuses[i]
    
    # 如果步骤状态为活跃状态(未开始或进行中),返回该步骤
    if status in PlanStepStatus.get_active_statuses():
        return i, step_info

# 如果没找到活跃步骤,返回 None
return None, None

其中 get_active_statuses() 返回 ["not_started", "in_progress"],意味着只有当所有步骤都是 "completed""blocked" 状态时,计划才会结束。

3. 计划结束处理

当没有更多活跃步骤时,会调用 _finalize_plan() 方法:

async def _finalize_plan(self) -> str:
    """使用 LLM 生成计划完成总结"""
    plan_text = await self._get_plan_text()
    
    # 使用 LLM 生成总结
    system_message = Message.system_message(
        "You are a planning assistant. Your task is to summarize the completed plan."
    )
    
    user_message = Message.user_message(
        f"The plan has been completed. Here is the final plan status:\n\n{plan_text}\n\nPlease provide a summary of what was accomplished and any final thoughts."
    )
    
    response = await self.llm.ask(messages=[user_message], system_msgs=[system_message])
    return f"Plan completed:\n\n{response}"

Gemini CLI 的停止逻辑

Gemini CLI 的设计思路完全不同,它用了一个更优雅但也更复杂的方案。

subagent 的停止逻辑

1. 达到最大轮次(MAX_TURNS)

if (this.runConfig.max_turns && turnCounter >= this.runConfig.max_turns) {
    this.output.terminate_reason = SubagentTerminateMode.MAX_TURNS;
    break;
}

这是最简单的保护机制,防止无限循环。

2. 执行超时(TIMEOUT)

let durationMin = (Date.now() - startTime) / (1000 * 60);
if (durationMin >= this.runConfig.max_time_minutes) {
    this.output.terminate_reason = SubagentTerminateMode.TIMEOUT;
    break;
}

注意这里检查了两次超时:

  • 在调用 LLM 之前检查一次
  • 在调用 LLM 之后又检查一次

这是因为 LLM 调用可能很耗时,要确保不会超时太多。

3. 用户中断(通过 AbortSignal)

if (abortController.signal.aborted) return;

这个检查出现在 stream 处理循环里,确保能及时响应用户的取消操作。

4. 错误异常(ERROR)

catch (error) {
    console.error('Error during subagent execution:', error);
    this.output.terminate_reason = SubagentTerminateMode.ERROR;
    throw error;
}

任何未捕获的异常都会导致停止。

5. 目标完成(GOAL)

目标完成的判断分两种情况:

情况A:没有预定输出要求

if (!this.outputConfig || Object.keys(this.outputConfig.outputs).length === 0) {
    // 没有要求特定输出,LLM 不调用工具就认为完成了
    if (functionCalls.length === 0) {
        this.output.terminate_reason = SubagentTerminateMode.GOAL;
        break;
    }
}

情况B:有预定输出要求

// 检查是否所有要求的变量都已输出
const remainingVars = Object.keys(this.outputConfig.outputs).filter(
    (key) => !(key in this.output.emitted_vars)
);

if (remainingVars.length === 0) {
    this.output.terminate_reason = SubagentTerminateMode.GOAL;
    break;
}

声明式输出系统的实现

声明式输出系统的核心是 outputConfig

// 预先声明需要什么输出
this.outputConfig = {
    outputs: {
        "summary""string",
        "recommendations""array", 
        "risk_score""number"
    }
};

// Agent 通过 self.emitvalue 工具来产生输出
// 每次调用会把值存到 this.output.emitted_vars 里
this.output.emitted_vars = {
    "summary""这是总结...",
    "recommendations": ["建议1""建议2"]
    // risk_score 还没输出
};

系统会不断检查 emitted_vars 是否包含了所有 outputs 中声明的变量。只有全部输出了才认为目标完成。

Nudge 机制

Nudge(轻推)机制代码:

if (functionCalls.length === 0) {  // LLM 停止调用工具了
    // 检查是否还有变量没输出
    const remainingVars = Object.keys(this.outputConfig.outputs).filter(
        (key) => !(key in this.output.emitted_vars)
    );
    
    if (remainingVars.length > 0) {
        // 还有变量没输出,"推"它一下
        const nudgeMessage = `You have stopped calling tools but have not emitted 
        the following required variables: ${remainingVars.join(', ')}. 
        Please use the 'self.emitvalue' tool to emit them now, 
        or continue working if necessary.`;
        
        // 把提醒作为新的用户消息发给 LLM
        currentMessages = [{
            role: 'user',
            parts: [{ text: nudgeMessage }]
        }];
        
        // 继续循环,不退出
    }
}

完整的 subagent 执行流程

开始
  ↓
while (true) {
  检查是否超时/超轮次 → 是 → 退出
    ↓ 否
  调用 LLM
    ↓
  LLM 返回工具调用?
    ├─ 是 → 执行工具 → 检查目标是否完成
    │         ├─ 是 → 退出
    │         └─ 否 → 继续循环
    │
    └─ 否(LLM 停止调用工具)
         ↓
       有预定输出要求吗?
         ├─ 没有 → 退出(认为完成)
         └─ 有 → 检查是否都输出了
                   ├─ 是 → 退出
                   └─ 否 → Nudge 提醒 → 继续循环
}

三层循环检测机制

第一层:工具调用重复检测

这是最简单直接的检测,针对 Agent 反复调用相同工具的情况。

private checkToolCallLoop(toolCall: { name: string; args: object }): boolean {
    // 把工具名和参数一起哈希,生成唯一标识
    const key = this.getToolCallKey(toolCall);
    
    if (this.lastToolCallKey === key) {
        // 和上次调用完全一样,计数+1
        this.toolCallRepetitionCount++;
    } else {
        // 不一样,重置计数
        this.lastToolCallKey = key;
        this.toolCallRepetitionCount = 1;
    }
    
    // 连续5次调用相同工具+相同参数 = 循环
    if (this.toolCallRepetitionCount >= TOOL_CALL_LOOP_THRESHOLD) {
        return true;
    }
}

触发条件:连续 5 次调用完全相同的工具(包括参数)。

这种检测很严格——必须是连续的、完全相同的调用。如果中间插入了其他工具调用,计数就会重置。

第二层:内容重复检测(”咒语”检测)

这是最复杂的部分,用来检测 LLM 输出重复内容的情况,就像在念咒语一样。

private checkContentLoop(content: string): boolean {
    // 1. 先检查是否在特殊内容块中(代码块、表格、列表等)
    const numFences = (content.match(/```/g) ?? []).length;
    const hasTable = /(^|\n)\s*(\|.*\||[|+-]{3,})/.test(content);
    // ... 检查各种格式
    
    // 在代码块中不检测循环(代码本来就可能有重复)
    if (this.inCodeBlock) {
        return false;
    }
    
    // 2. 把新内容加入历史
    this.streamContentHistory += content;
    
    // 3. 保持历史在 1000 字符以内
    this.truncateAndUpdate();
    
    // 4. 分析内容块是否重复
    return this.analyzeContentChunksForLoop();
}

核心算法是滑动窗口 + 哈希检测

private analyzeContentChunksForLoop(): boolean {
    while (this.hasMoreChunksToProcess()) {
        // 提取 50 字符的块
        const currentChunk = this.streamContentHistory.substring(
            this.lastContentIndex,
            this.lastContentIndex + CONTENT_CHUNK_SIZE  // 50
        );
        
        // 计算哈希
        const chunkHash = createHash('sha256').update(currentChunk).digest('hex');
        
        // 检查这个块是否重复出现
        if (this.isLoopDetectedForChunk(currentChunk, chunkHash)) {
            return true;
        }
        
        // 滑动窗口向前移动 1 个字符
        this.lastContentIndex++;
    }
}

判断循环的条件:

private isLoopDetectedForChunk(chunk: string, hash: string): boolean {
    const existingIndices = this.contentStats.get(hash);
    
    if (!existingIndices) {
        // 第一次见到这个块,记录位置
        this.contentStats.set(hash, [this.lastContentIndex]);
        return false;
    }
    
    // 验证内容确实相同(防止哈希碰撞)
    if (!this.isActualContentMatch(chunk, existingIndices[0])) {
        return false;
    }
    
    existingIndices.push(this.lastContentIndex);
    
    // 需要出现至少 10 次
    if (existingIndices.length < CONTENT_LOOP_THRESHOLD) {  // 10
        return false;
    }
    
    // 关键:这 10 次必须距离很近(平均距离 ≤ 75 字符)
    const recentIndices = existingIndices.slice(-CONTENT_LOOP_THRESHOLD);
    const totalDistance = recentIndices[recentIndices.length - 1] - recentIndices[0];
    const averageDistance = totalDistance / (CONTENT_LOOP_THRESHOLD - 1);
    const maxAllowedDistance = CONTENT_CHUNK_SIZE * 1.5;  // 75
    
    return averageDistance <= maxAllowedDistance;
}

触发条件:同一个 50 字符的内容块,在很短的距离内重复出现 10 次

第三层:LLM 智能检测

这是最高级的检测,用 AI 来判断 AI 是否陷入循环。

private async checkForLoopWithLLM(signal: AbortSignal) {
    // 取最近 20 轮对话
    const recentHistory = this.config
        .getGeminiClient()
        .getHistory()
        .slice(-LLM_LOOP_CHECK_HISTORY_COUNT);  // 20
    
    // 清理历史(去掉悬空的函数调用等)
    const trimmedHistory = this.trimRecentHistory(recentHistory);
    
    // 让 Gemini Flash 模型分析
    const result = await this.config.getBaseLlmClient().generateJson({
        contents: [...trimmedHistory, { role: 'user', parts: [{ text: taskPrompt }] }],
        schema: {
            type'object',
            properties: {
                reasoning: { type'string' },
                confidence: { type'number' }  // 0-1 之间
            }
        },
        model: DEFAULT_GEMINI_FLASH_MODEL,
        systemInstruction: LOOP_DETECTION_SYSTEM_PROMPT
    });
    
    if (result['confidence'] > 0.9) {
        // 高置信度认为是循环
        console.warn(result['reasoning']);
        return true;
    }
}

触发时机:

async turnStarted(signal: AbortSignal) {
    this.turnsInCurrentPrompt++;
    
    if (
        this.turnsInCurrentPrompt >= LLM_CHECK_AFTER_TURNS &&  // 至少 30 轮
        this.turnsInCurrentPrompt - this.lastCheckTurn >= this.llmCheckInterval
    ) {
        this.lastCheckTurn = this.turnsInCurrentPrompt;
        return await this.checkForLoopWithLLM(signal);
    }
}
  • 必须执行超过 30 轮才开始检查(避免误判)
  • 不是每轮都检查,有间隔(默认 3 轮)
  • 间隔会根据置信度动态调整(5-15 轮)
// 动态调整检查频率
this.llmCheckInterval = Math.round(
    MIN_LLM_CHECK_INTERVAL +  // 5
    (MAX_LLM_CHECK_INTERVAL - MIN_LLM_CHECK_INTERVAL) * (1 - result['confidence'])
    // 置信度越高,检查越频繁
);

三种循环类型

系统定义了三种循环类型:

enum LoopType {
    CONSECUTIVE_IDENTICAL_TOOL_CALLS,  // 连续相同工具调用
    CHANTING_IDENTICAL_SENTENCES,      // 重复输出相同内容
    LLM_DETECTED_LOOP                  // LLM 检测到的逻辑循环
}

每种都有不同的检测方法和触发条件。

这比较适合处理长对话场景,既能有效检测循环,又不会因为过于敏感而误判正常的迭代操作。

小结

AI Agent 的停止策略是一个容易被忽视但极其重要的技术问题。从原理上看,Agent 就是一个大循环,不断调用 LLM 和工具来完成任务,但如果没有合理的停止机制,就会出现无限循环浪费资源,或者过早停止无法完成任务的问题。常见的停止方案包括硬性限制(步数、时间、API调用次数)、任务完成检测、显式停止信号、循环检测、错误累积和用户中断等,实际应用中需要组合使用多种策略。

OpenManus 采用了相对简单直接的设计:给每个 Agent 配备 terminate 工具,让 LLM 自己决定何时停止,同时用状态机管理生命周期,配合步数限制作为保底,并确保无论如何停止都会正确清理资源。

而 Gemini CLI 的设计更加精巧,核心是声明式输出系统——预先定义需要什么输出,只有全部输出才算完成,如果 Agent 停止调用工具但还有变量未输出,系统会通过 Nudge 机制温和提醒;在循环检测上,Gemini 实现了三层防护:工具调用重复检测(连续5次相同调用)、内容重复检测(滑动窗口+哈希算法检测”咒语”现象)、以及用 LLM 分析对话历史判断是否陷入逻辑循环。

实践中的关键是不要依赖单一停止机制,要组合使用多种策略形成多层防护,给 LLM 明确的停止指引,为不同类型的停止原因提供清晰的用户反馈,并确保资源能够可靠清理。停止策略的本质是在”让 Agent 完成任务”和”防止失控”之间找到平衡点。

以上。

关于 AI Agent: 从 Manus 聊起

最近几天 Manus 的新闻不停的出现在我的信息流中,从 Manus 官方账号清空了微博、小红书上的所有内容,到裁员争议,据说将核心技术人员迁往了新加坡。一个从北京、武汉出发的纯正中国公司,最终选择了离开。

还记得今年火热的 3 月,Manus 发布当天就引爆了社交网络。邀请码一码难求,甚至在二手平台被炒出天价。创始人肖弘(人称red)带领的这支年轻团队,用一个产品点燃了整个行业对 AI Agent 的热情。

2025 年是 AI Agent 元年。AI Agent 的发展速度惊人。不只是 Manus 这种通用型 Agent,还有各种垂直领域的,如 设计领域的 lovart,编程领域的 Claude Code Cursor 等等。

那什么是 AI Agent,它由什么组成?今天我们聊一聊。

1. 从专家系统说起

要说 AI Agent 的历史,得从上世纪 60 年代说起。那时候,计算机科学家们就在琢磨一个事:能不能让机器像人一样,自己去感知环境、做出决策、然后采取行动?

最早的尝试是专家系统。比如 1970 年代斯坦福大学开发的 MYCIN,这是一个诊断血液感染疾病的系统。它的工作方式很简单:问医生一堆问题,然后根据预设的规则给出诊断建议。虽然现在看来很原始,但在当时,这已经算是「智能」了。

到了 80 年代,出现了更复杂的系统,比如 R1/XCON,帮助 DEC 公司配置计算机系统。这些系统有个共同特点:它们都是基于规则的。你得事先把所有可能的情况都想到,然后写成 if-then 规则。问题是,现实世界太复杂了,你不可能把所有情况都考虑进去。

90 年代,研究者们开始尝试新的路子。他们发现,与其让人去编写所有规则,不如让机器自己去学习。于是有了基于机器学习的 Agent,比如强化学习 Agent。这些 Agent 可以通过试错来学习如何完成任务。

但真正的转折点出现在 2010 年代深度学习兴起之后。特别是 2017 年 Transformer 架构的出现,彻底改变了游戏规则。有了 GPT、BERT 这些大语言模型,AI Agent 突然变得「聪明」了很多。它们不再需要人类事先编写规则,而是可以理解自然语言,根据上下文做出合理的判断。

2. 现代 AI Agent 的样子

要理解现代的 AI Agent,我们得先搞清楚它到底是什么。

简单来说,AI Agent 就是一个能够自主感知环境、制定计划、采取行动来完成特定目标的系统。听起来很抽象?我举个例子:

假设你要让 AI 帮你订一张从北京到上海的机票。一个简单的聊天机器人可能只会回答:”请您登录航空公司网站自行预订。”但一个真正的 AI Agent 会这样做:

  1. 理解你的需求(什么时间、预算多少、有什么偏好)
  2. 搜索多个航空公司的航班信息
  3. 比较价格和时间
  4. 根据你的偏好筛选
  5. 甚至可能帮你完成预订(如果有相应的接口)

这就是 AI Agent 和普通 AI 应用的区别:它不是被动地回答问题,而是主动地解决问题

3. 核心技术和架构

现在咱们来看看 AI Agent 是怎么实现的。核心架构可以分成四个部分:

3.1 感知模块

这是 Agent 的「眼睛」和「耳朵」。它需要理解用户的输入,同时还要感知环境的状态。比如,当你让 AI Agent 帮你写代码时,它需要理解:

  • 你想要实现什么功能
  • 使用什么编程语言
  • 有什么特殊要求
  • 当前的代码结构是什么样的

但这里有个关键点:感知模块需要区分两种不同的信息——状态上下文和意图上下文。

状态上下文是环境的客观信息。比如:

  • 当前项目使用的是 Python 3.9
  • 代码库里已经有了用户认证模块
  • 数据库是 MySQL
  • 使用的是的 FastAPI 框架

这些信息是确定的、可验证的事实。Agent 需要准确地获取和理解这些信息,因为任何误判都可能导致后续行动的失败。

意图上下文则是用户想要达成的目标,这往往更加模糊和主观。比如用户说「帮我优化一下这段代码」,Agent 需要理解:

  • 「优化」是指性能优化还是可读性优化?
  • 用户的性能预期是什么?
  • 有没有特定的约束条件?

区分这两种上下文至关重要。很多 AI Agent 的失败,就是因为混淆了状态和意图。比如,用户说「这个函数太慢了」,Agent 需要识别出:

  • 状态:函数执行时间是 500ms
  • 意图:用户希望降低到 100ms 以内

现代的 AI Agent 通过多种方式来增强感知能力:

多模态感知:不只是文字,还包括图像、语音、甚至视频。Cursor 支持图片上传,代码索引,文档等。

主动询问:当信息不充分时,优秀的 Agent 会主动提问。「你提到要优化性能,具体是想优化响应时间还是内存占用?」这种澄清式的对话,能大大提高后续行动的准确性。

历史记录:通过分析用户的历史行为,Agent 可以更好地理解当前的意图。如果用户之前多次要求「简洁的代码」,那么在新的任务中,Agent 就会倾向于生成更简洁的解决方案。

环境探测:高级的 Agent 还会主动探测环境。比如,在开始写代码前,它可能会先检查项目的配置文件、依赖列表、测试用例等,构建一个完整的环境画像。

感知的准确性直接决定了 Agent 的表现。一个看不清路的司机,再好的驾驶技术也没用。同样,一个不能准确理解用户意图和环境状态的 Agent,后续的规划和执行必然会出问题。

3.2 推理模块

这是 Agent 的「大脑」,也就是大语言模型。现在主流的做法是使用 GPT-4、Claude、Gemini 这样的大模型。但光有大模型还不够,还需要深入理解不同模型的特性,才能让它们更好地工作。

模型的性格差异

就像人有不同的性格,AI 模型也有各自的「个性」。这不是玄学,而是训练方式和优化目标的差异造成的。

以 Cursor 编辑器的实践为例,他们把模型分为两大类:

思考型模型(Thinking Models):

  • Claude 3 Opus:喜欢先理解全局,会主动推断你的意图
  • Gemini 2.0 Flash:自信果断,经常会做出超出预期的大改动
  • o1:专门为复杂推理设计,会花时间深入分析问题

这类模型就像经验丰富的专家,你给它一个大方向,它会自己规划路径。适合探索性任务、大规模重构、或者当你自己也不太确定最终方案时使用。

执行型模型(Non-thinking Models):

  • Claude 3.5 Sonnet:等待明确指令,不会过度推断
  • GPT-4 Turbo:行为可预测,适合精确控制
  • 文心一言 4.0:在中文任务上表现稳定

这类模型像是可靠的助手,严格按照你的要求执行。适合明确的任务、需要精确控制的场景、或者当你已经知道要做什么时使用。

选择模型的艺术

选择合适的模型,就像选择合适的工具。你不会用大锤子去拧螺丝,也不会用螺丝刀去砸钉子。

根据任务类型选择

  • 代码生成:Claude 3.5 Sonnet 和 GPT-4 都很优秀
  • 代码理解和重构:Gemini 2.0 Flash 的长上下文能力突出
  • 复杂 Bug 调试:o1 的深度推理能力更适合
  • 中文文档处理:通义千问、豆包有本土优势

根据交互风格选择

  • 如果你喜欢给出详细指令:选择执行型模型
  • 如果你偏好给出大方向:选择思考型模型
  • 如果你需要创造性方案:选择更”活跃”的模型
  • 如果你需要稳定输出:选择更”保守”的模型

提示词工程的进化

早期的 AI Agent 严重依赖精心设计的提示词。但随着模型能力的提升,这种情况正在改变。

从复杂到简单: 过去我们可能需要这样的提示词:

你是一个专业的Python开发者。请严格遵循PEP8规范。
在编写代码时,请考虑以下几点:
1. 代码的可读性
2. 性能优化
3. 错误处理
...(还有20条)

现在,一句简单的”帮我优化这段代码”就能得到不错的结果。

动态提示词策略: 现代 AI Agent 会根据上下文动态调整提示词:

  • 初次对话:使用更详细的背景说明
  • 后续对话:只提供增量信息
  • 错误修正:加入具体的约束条件
  • 创造性任务:减少限制,鼓励探索

混合模型策略

越来越多的 AI Agent 开始采用混合模型策略,在一个任务流程中使用多个模型:

  1. 理解阶段:使用 Claude 3 Opus 深入分析需求
  2. 规划阶段:使用 o1 制定详细的执行计划
  3. 执行阶段:使用 GPT-4 Turbo 快速生成代码
  4. 优化阶段:使用专门的代码模型进行微调

这种方式能够充分发挥每个模型的优势,同时控制成本。

3.3 记忆模块

人做事情需要记住前面发生了什么,AI Agent 也一样。记忆分成几种:

  • 短期记忆:当前对话的上下文
  • 长期记忆:之前的对话历史、学到的知识
  • 工作记忆:执行任务过程中的中间状态

但实现一个好的记忆系统,比想象中要复杂得多。

3.3.1 记忆的层次结构

就像人脑有不同类型的记忆,AI Agent 的记忆系统也需要分层设计:

感知记忆(Sensory Memory)

  • 保存时间:几秒到几分钟
  • 内容:用户刚刚的输入、系统刚刚的输出
  • 用途:处理连续对话中的指代关系

工作记忆(Working Memory)

  • 保存时间:整个任务周期
  • 内容:当前任务的状态、中间结果、待办事项
  • 用途:复杂任务的分步执行

情景记忆(Episodic Memory)

  • 保存时间:数天到数月
  • 内容:完整的对话历史、任务执行记录
  • 用途:理解用户偏好、避免重复错误

语义记忆(Semantic Memory)

  • 保存时间:永久
  • 内容:领域知识、最佳实践、学到的模式
  • 用途:积累经验、提升能力

3.3.2 实现方案-RAG(检索增强生成)

这是目前最成熟的方案。基本思路是:当 AI Agent 需要回答问题时,先去知识库里检索相关信息,然后把这些信息作为上下文提供给大模型。

比如你问:”我们公司的年假政策是什么?”Agent 会先去检索公司的政策文档,找到相关内容,然后基于这些内容生成回答。

RAG 的进化史

第一代 RAG(2020-2022):

  • 简单的向量检索
  • 使用 BERT 或 Sentence-BERT 做编码
  • 召回 Top-K 相关文档
  • 效果一般,经常找不到真正相关的内容

第二代 RAG(2023-2024):

  • 引入混合检索(向量+关键词)
  • 使用更强的编码模型(如 BGE、E5)
  • 加入重排序(Reranking)步骤
  • 开始考虑文档结构和语义分块

第三代 RAG(2024-现在):

  • 多级索引结构(摘要→章节→段落)
  • 查询改写和扩展
  • 动态上下文窗口调整
  • 引入知识图谱增强检索

实践中的 RAG 优化技巧

  1. 智能分块:不要机械地按字数切分,而是按语义单元切分

    • 代码:按函数/类切分
    • 文档:按章节/段落切分
    • 对话:按话轮切分
  2. 多路召回:同时使用多种检索策略

    • 向量相似度检索
    • BM25 关键词检索
    • 实体链接检索
    • 基于图的检索
  3. 上下文工程:检索到的内容需要精心组织

  4. 增量索引:新知识的实时更新

    • 使用流式处理架构
    • 支持热更新索引
    • 版本控制和回滚机制

3.3.3 实现方案-超长上下文

最新的趋势是直接增加模型的上下文长度。比如 Claude 3 已经支持 200K token 的上下文,Gemini 1.5 Pro 甚至支持 200 万 token。

长上下文的真实挑战

虽然模型号称支持超长上下文,但实际使用中会遇到很多问题:

  1. 「迷失在中间」现象:模型对上下文开头和结尾的内容记忆较好,但中间部分容易遗忘

  2. 注意力稀释:上下文越长,模型对每个部分的注意力就越分散

  3. 推理退化:在超长上下文中,模型的推理能力会显著下降

混合方案:长上下文 + 选择性注意

3.3.4 主动遗忘

这是一个反直觉但很重要的概念:不是记住越多越好,而是要学会遗忘。

为什么需要遗忘?

  1. 降噪:不是所有信息都值得记住
  2. 隐私:某些敏感信息需要及时删除
  3. 效率:保持记忆系统的高效运转
  4. 相关性:过时的信息可能产生负面影响

遗忘策略

  1. 基于时间的遗忘

    • 会话结束后 24 小时删除临时信息
    • 30 天后归档低频访问的记忆
    • 90 天后删除无用的错误记录
  2. 基于重要性的遗忘

    • 使用 LRU(最近最少使用)策略
    • 基于访问频率的动态评分
    • 保留高价值的”关键时刻”
  3. 基于相关性的遗忘

    • 当新信息与旧信息冲突时,更新而非累加
    • 合并相似的记忆,避免冗余
    • 定期整理和压缩记忆库

3.4 行动模块

这是 Agent 的「手脚」,让它能够真正做事情。

行动模块的核心是 Function Calling(函数调用),这是目前最主流的方式。

简单来说,就是预先定义好一系列函数,比如搜索网页、查询数据库、发送邮件等,然后告诉大模型这些函数的作用和参数。

当用户提出需求时,模型会判断需要调用哪个函数,提取相应的参数,执行函数并获取结果。这个过程已经从最初的单次调用,进化到现在可以进行多步骤调用、并行执行、错误重试等复杂操作。

Anthropic 推出的 MCP(Model Context Protocol)协议,试图建立行动模块的统一标准。

MCP 采用服务器-客户端架构,工具提供方作为服务器,AI 应用作为客户端,通过标准化的协议进行通信。这种设计的好处是解耦和复用:AI 应用不需要知道工具的具体实现细节,一个 MCP 服务器可以同时服务多个 AI 应用。更重要的是,MCP 提供了统一的安全管理和权限控制机制,让行动模块的集成变得更加简单和安全。

安全性是行动模块最关键的考虑因素。

在 Manus 刚上线的时候就爆出一个问题,有人用提示词,让 AI Agent 打包了当前执行的环境的所有代码。

给 AI 执行能力就像给孩子一把剪刀,必须设置严格的安全边界。现代 Agent 通常采用多层防护:首先是沙箱环境,所有代码执行都在隔离的容器中进行,限制内存、CPU、网络访问;其次是权限管理,基于用户角色和具体场景动态分配权限;最后是审计日志,记录所有行动的执行情况,便于追溯和分析。这些措施确保 Agent 在帮助用户的同时,不会造成安全风险。

复杂任务往往需要多个行动的协调配合,这就需要工作流引擎来编排。比如”帮我分析竞品并生成报告”这个任务,可能需要先搜索竞品信息,然后提取关键数据,接着进行对比分析,最后生成可视化报告。工作流引擎负责管理这些步骤的执行顺序、处理步骤间的数据传递、应对执行失败等情况。高级的引擎还支持条件分支、循环执行、并行处理等复杂逻辑,让 Agent 能够处理更加复杂的任务。

行动模块的未来发展方向是更强的自主性。目前的 Agent 主要执行预定义的动作,但未来可能会具备动作发现和学习能力,能够自动发现新的 API、学习新的操作模式、甚至创造性地组合已有动作来完成新任务。另一个重要方向是与物理世界的交互,通过机器人或 IoT 设备执行物理动作。随着这些能力的提升,AI Agent 将真正从「会说」进化到「会做」,成为人类在数字世界和物理世界中的得力助手。

4. 当前的局限性

AI 有其局限性,了解这些局限性,对于合理使用和未来改进都很重要。

4.1 幻觉问题

这是所有基于大模型的 AI Agent 都面临的核心挑战。

Agent 可能会编造不存在的 API、虚构执行结果、或者对自己的能力过度自信。比如,当你要求 Agent 查询某个数据库时,它可能会返回看起来合理但实际上完全虚构的数据。

这种幻觉在连续多步骤的任务中会被放大,一个小错误可能导致整个任务链的崩溃。

更危险的是,这些幻觉往往很难被发现,因为它们在表面上看起来完全合理。虽然通过 RAG、工具调用验证等方法可以部分缓解,但彻底解决仍然是个开放性难题。

4.2 可靠性不足

AI Agent 的表现稳定性仍然是个大问题。

同样的任务,在不同时间执行可能得到不同的结果。

这种不确定性来源于多个方面:模型本身的随机性、上下文理解的偏差、外部环境的变化等。

在一些对可靠性要求高的场景,比如金融交易、医疗诊断、工业控制等,目前的 AI Agent 还远远达不到要求。

即使加入了重试机制、结果验证等保障措施,也只能提高而非保证可靠性。这导致在关键业务场景中,AI Agent 更多是作为辅助而非主导。

4.3 成本与效率

运行一个功能完善的 AI Agent 系统成本不菲。

首先是模型调用成本,特别是使用 GPT-4、Claude 等顶级模型时,每次交互都要花费不少钱。复杂任务可能需要多次模型调用,成本会快速累加。

其次是延迟问题,每次函数调用、每次推理都需要时间,一个看似简单的任务可能需要等待数十秒甚至几分钟。对于需要实时响应的场景,当前的 AI Agent 往往力不从心。

虽然可以通过模型蒸馏、缓存优化等手段降低成本,但在性能和成本之间找到平衡点仍然很困难。

4.4 安全与隐私挑战

AI Agent 需要访问大量数据和系统才能发挥作用,这带来了严重的安全隐私问题。

首先是数据泄露风险,Agent 可能无意中将敏感信息包含在给大模型的请求中,而这些数据可能被用于模型训练。

其次是提示注入攻击,恶意用户可能通过精心构造的输入操控 Agent 执行非预期的操作。

还有权限滥用问题,一个被赋予过多权限的 Agent 可能造成严重损害。

虽然业界正在开发各种防护措施,如差分隐私、安全沙箱、细粒度权限控制等,但攻防对抗仍在继续。

4.5 理解与推理的局限

虽然大模型展现出了惊人的能力,但 AI Agent 在深层理解和复杂推理方面仍有明显不足。它们往往只能处理相对直接的任务,面对需要长链条推理、创造性思维或深层次理解的问题时就会暴露短板。

比如,Agent 可能很擅长执行”帮我订一张机票”这样的任务,但如果要求”帮我规划一个考虑预算、时间、个人兴趣的完整旅行方案”,效果就会大打折扣。

此外,Agent 缺乏真正的常识推理能力,可能会提出一些违背基本常识的方案。即使是最新的 o1 模型,虽然推理能力有所提升,但距离人类水平的推理能力还有很大差距。

5. 写在最后

AI Agent 的发展才刚刚开始。虽然现在的技术还不完美,但进步的速度是惊人的。两年前,我们还在惊叹 ChatGPT 能够进行对话;现在,AI Agent 已经能够帮我们写代码、分析数据、制定计划了。

对于技术人员来说,现在是最好的时代。我们有机会参与到这场变革中,创造出真正有用的 AI Agent。但同时,我们也要保持清醒:AI Agent 是工具,不是魔法。它能够提高效率,但不能替代人类的创造力和判断力。

未来的世界,可能每个人都会有自己的 AI Agent 团队。就像现在每个人都有智能手机一样自然。

而现在,正是这个未来的开端。

以上。