欢迎光临
我们一直在努力

当AI开始替你上班:大模型Agent如何重塑工作流自动化

当AI开始替你上班:大模型Agent如何重塑工作流自动化

大家好,我是摘星。今天我们来聊一个正在真实发生、但很多人还没完全意识到的变化——AI Agent正在从"对话玩具"变成真正的"数字同事"。

过去一年多,我接触了上百个企业和个人开发者,他们几乎都经历了同样的阶段:兴奋地接入大模型API,调出GPT-4或Claude的界面,问几个问题,觉得好厉害;然后花几周甚至几个月优化Prompt、调参数,结果发现——AI能回答问题,但不能替你完成工作。因为真正的工作从来不是一次问答,而是多步骤、多系统、多判断的连续流程。

这篇文章,我会把AI Agent的核心概念、技术原理、实战架构全部拆开揉碎来讲。读完你会知道:Agent不是什么黑科技,它是一套可以让大模型真正"动起来"的工程化方法论;你也会得到可以直接复用的代码框架、踩过的坑、以及如何用国内可访问的模型搭建自己的Agent系统。


一、为什么大模型原生就是"残疾人"

大模型很强,但原生大模型有一个根本限制:它只能处理单次交互。

你给它一段输入,它给你一段输出,然后就结束了。它不知道你上一次问的是什么,不记得你昨天让它做过什么,更不会在你说"继续"的时候自动接着往下走。这就像你雇了一个天才实习生,但他每次只干一件事,而且干完就失忆。

我们来看一个具体场景:

场景:自动写周报

你的需求是:每天早上8点,自动从飞书获取你上一周的工作消息,提取关键事件,生成周报初稿,发到你邮箱。

用原生大模型API,你需要怎么做?你需要自己写一个程序,每一步都手动调用:

# 你需要手动编排的流程(伪代码)
# 第1步:从飞书拉取消息
messages = feishu.get_messages(user_id, start_date, end_date)

# 第2步:把消息格式化成prompt
prompt = f"以下是本周工作消息:{messages}\\n请总结关键事件"

# 第3步:调用大模型API
summary = llm.invoke(prompt)

# 第4步:生成周报格式
report_prompt = f"将以下总结整理成周报格式:{summary}"
report = llm.invoke(report_prompt)

# 第5步:发送邮件
email.send(report)

这段代码看起来不复杂,但你忽略了一个关键问题:如果第3步生成的总结质量不好怎么办?如果飞书API返回的数据格式变了怎么办?如果邮件发送失败了怎么办?

原生大模型不会处理这些异常。它只会给你一个输出,然后结束。而真实的业务场景中,每一个步骤都可能出错,每一个分支都需要判断,每一个循环都可能需要重试。

这就是Agent要解决的问题。


二、AI Agent到底是什么

AI Agent(人工智能代理)并不是一个新概念,但在LLM时代,它的含义发生了根本性变化。

简单说:Agent是大模型+规划+记忆+工具使用的组合,让模型能够自主决定做什么、怎么做、什么时候停下来。

我们来看一个标准化的Agent架构图:

渲染错误: Mermaid 渲染失败: Parse error on line 32:
…"反思当前状态|loop|回到规划器"
———————-^
Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got '1'

规划器(Planning) 是Agent的"大脑前额叶"。它负责把一个模糊的大任务拆解成具体的子任务,决定先做什么后做什么,并且在执行过程中不断反思:“我现在做的事对吗?需不需要调整?”

记忆系统(Memory) 是Agent的"海马体"。它分三种:

  • 短期记忆:当前对话窗口内的上下文
  • 情景记忆:历史执行记录、用户偏好
  • 语义记忆:结构化的知识、规则、协议

工具箱(Tools) 是Agent的"四肢"。大模型本身只会说话,但现实世界需要Agent能够:查数据库、调用API、发邮件、执行代码、操作文件、浏览网页。

执行器(Executor) 是Agent的"手"。它负责真正调用工具、收集结果、把信息反馈给规划器。

这四个模块形成了一个循环:规划→记忆→工具调用→执行→把结果写回记忆→再规划。Agent不是一条直线跑到底,而是一个不断反思和调整的智能循环。


三、ReAct范式:让大模型学会"思考"

说到Agent,很多人会提到ReAct(Reasoning + Acting)。这是2023年由普林斯顿和Google DeepMind联合提出的一个框架,论文标题是《ReAct: Synergizing Reasoning and Acting in Language Models》。

ReAct解决的核心问题是:大模型光会推理不够,还要会行动;光会行动也不够,还要有推理来指导行动。

举一个典型案例。假设你问Agent:“帮我查一下今天北京的天气,如果下雨就带伞,不下雨就不带。”

一个只有Action能力的Agent会直接调用天气API,然后返回结果。但它不知道该不该带伞,因为它没有"Reasoning"的过程。

而ReAct Agent的思考链条是这样的:

Thought: 用户想知道今天北京的天气,并且需要我给出是否带伞的建议。
Action: 调用天气API获取北京当前天气。
Observation: 天气API返回:今天北京小雨,气温15-18度,有偏北风3-4级。
Thought: 根据API返回的数据,今天确实下雨,而且气温偏低。
Action: 生成回复:今天北京有小雨,气温15-18度,建议带伞,同时可以穿一件薄外套。

注意这个"Thought"——它不是给用户看的,而是Agent内部推理过程的一部分。这个推理过程让Agent能够:

  • 理解当前任务的真正目标
  • 判断观察到的信息是否足够做出决策
  • 如果信息不够,主动决定下一步行动
  • ReAct的prompt模板大致如下:

    REACT_PROMPT = """你是一个智能助手,需要通过工具来完成用户任务。

    你必须按照以下格式思考和行动:

    问题:{用户输入}
    Thought: 我需要思考用户真正想要什么,当前的状态是什么
    Action: {tool_name} # 可选工具:search, calculator, weather, email, etc.
    Action Input: {tool_input}
    Observation: {工具返回的结果}
    … (这个循环可以重复多次)
    Thought: 现在我有足够的信息来回答用户问题了
    Final Answer: {给用户的最终回答}

    开始!

    问题:{input}
    """

    这个格式看起来简单,但它解决了一个根本问题:它让大模型能够显式地表达推理过程,而不是直接跳到答案。这不仅让Agent更可靠,也让我们能够审查Agent的决策过程,在出错时知道问题出在哪里。


    四、实战:用国产大模型搭建一个自动周报Agent

    接下来我们进入实操环节。我会用国内可访问的模型(文心一言/通义千问/DeepSeek等)作为后端,手把手搭建一个完整的自动周报生成Agent。

    4.1 系统架构

    #mermaid-svg-raNicljGG3maBJLG{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-raNicljGG3maBJLG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-raNicljGG3maBJLG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-raNicljGG3maBJLG .error-icon{fill:#552222;}#mermaid-svg-raNicljGG3maBJLG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-raNicljGG3maBJLG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-raNicljGG3maBJLG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-raNicljGG3maBJLG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-raNicljGG3maBJLG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-raNicljGG3maBJLG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-raNicljGG3maBJLG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-raNicljGG3maBJLG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-raNicljGG3maBJLG .marker.cross{stroke:#333333;}#mermaid-svg-raNicljGG3maBJLG svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-raNicljGG3maBJLG p{margin:0;}#mermaid-svg-raNicljGG3maBJLG .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-raNicljGG3maBJLG .cluster-label text{fill:#333;}#mermaid-svg-raNicljGG3maBJLG .cluster-label span{color:#333;}#mermaid-svg-raNicljGG3maBJLG .cluster-label span p{background-color:transparent;}#mermaid-svg-raNicljGG3maBJLG .label text,#mermaid-svg-raNicljGG3maBJLG span{fill:#333;color:#333;}#mermaid-svg-raNicljGG3maBJLG .node rect,#mermaid-svg-raNicljGG3maBJLG .node circle,#mermaid-svg-raNicljGG3maBJLG .node ellipse,#mermaid-svg-raNicljGG3maBJLG .node polygon,#mermaid-svg-raNicljGG3maBJLG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-raNicljGG3maBJLG .rough-node .label text,#mermaid-svg-raNicljGG3maBJLG .node .label text,#mermaid-svg-raNicljGG3maBJLG .image-shape .label,#mermaid-svg-raNicljGG3maBJLG .icon-shape .label{text-anchor:middle;}#mermaid-svg-raNicljGG3maBJLG .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-raNicljGG3maBJLG .rough-node .label,#mermaid-svg-raNicljGG3maBJLG .node .label,#mermaid-svg-raNicljGG3maBJLG .image-shape .label,#mermaid-svg-raNicljGG3maBJLG .icon-shape .label{text-align:center;}#mermaid-svg-raNicljGG3maBJLG .node.clickable{cursor:pointer;}#mermaid-svg-raNicljGG3maBJLG .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-raNicljGG3maBJLG .arrowheadPath{fill:#333333;}#mermaid-svg-raNicljGG3maBJLG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-raNicljGG3maBJLG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-raNicljGG3maBJLG .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-raNicljGG3maBJLG .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-raNicljGG3maBJLG .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-raNicljGG3maBJLG .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-raNicljGG3maBJLG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-raNicljGG3maBJLG .cluster text{fill:#333;}#mermaid-svg-raNicljGG3maBJLG .cluster span{color:#333;}#mermaid-svg-raNicljGG3maBJLG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-raNicljGG3maBJLG .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-raNicljGG3maBJLG rect.text{fill:none;stroke-width:0;}#mermaid-svg-raNicljGG3maBJLG .icon-shape,#mermaid-svg-raNicljGG3maBJLG .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-raNicljGG3maBJLG .icon-shape p,#mermaid-svg-raNicljGG3maBJLG .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-raNicljGG3maBJLG .icon-shape .label rect,#mermaid-svg-raNicljGG3maBJLG .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-raNicljGG3maBJLG .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-raNicljGG3maBJLG .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-raNicljGG3maBJLG :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    定时触发 8:00

    飞书消息获取

    数据清洗与结构化

    大模型API

    周报生成

    邮件发送

    钉钉通知

    重试机制

    4.2 核心代码实现

    第一部分:工具定义

    Agent需要调用的每一个外部系统,都需要定义为一个"工具"。下面是飞书消息获取的工具定义:

    import json
    import httpx
    from typing import Optional
    from datetime import datetime, timedelta

    class FeishuClient:
    """飞书开放平台客户端"""

    def __init__(self, app_id: str, app_secret: str):
    self.app_id = app_id
    self.app_secret = app_secret
    self.base_url = "https://open.feishu.cn/open-apis"
    self._token: Optional[str] = None
    self._token_expire = None

    def get_access_token(self) > str:
    """获取tenant_access_token,带缓存"""
    # 同一个进程内,token有效期2小时,不需要每次调用都刷新
    if self._token and self._token_expire:
    if datetime.now() < self._token_expire:
    return self._token

    url = f"{self.base_url}/auth/v3/tenant_access_token/internal"
    payload = {"app_id": self.app_id, "app_secret": self.app_secret}
    resp = httpx.post(url, json=payload, timeout=30)
    resp.raise_for_status()
    data = resp.json()

    if data.get("code") != 0:
    raise RuntimeError(f"飞书获取token失败: {data.get('msg')}")

    self._token = data["tenant_access_token"]
    # token有效期2小时,提前5分钟刷新
    self._token_expire = datetime.now() + timedelta(hours=2) timedelta(minutes=5)
    return self._token

    def get_messages(
    self,
    user_id: str,
    start_time: int,
    end_time: int,
    page_size: int = 50
    ) > list[dict]:
    """
    获取用户在指定时间范围内的消息

    参数:
    user_id: 飞书用户的open_id
    start_time: 开始时间戳(秒)
    end_time: 结束时间戳(秒)
    page_size: 每页消息数,最大100

    返回:
    消息列表,每条消息包含: message_id, create_time, sender, content
    """
    token = self.get_access_token()
    headers = {"Authorization": f"Bearer {token}"}

    # 飞书获取消息的API是POST,而且参数要放在request body里
    url = f"{self.base_url}/im/v1/messages"
    params = {
    "container_id_type": "p2p", # 私聊会话
    "container_id": user_id,
    "start_time": start_time,
    "end_time": end_time,
    "page_size": page_size,
    }

    all_messages = []
    has_more = True
    page_token = None

    while has_more:
    if page_token:
    params["page_token"] = page_token

    # 注意:飞书API GET请求的参数是通过query string传递的,但body参数有限制
    # 这里实际上用POST /im/v1/messages 是更灵活的方案
    resp = httpx.get(
    url,
    headers=headers,
    params=params,
    timeout=30
    )
    resp.raise_for_status()
    data = resp.json()

    if data.get("code") != 0:
    raise RuntimeError(f"飞书获取消息失败: {data.get('msg')}")

    items = data.get("data", {}).get("items", [])
    all_messages.extend(items)
    has_more = data.get("data", {}).get("has_more", False)
    page_token = data.get("data", {}).get("page_token")

    return all_messages

    代码说明:

    • 这个类封装了飞书API的认证和消息获取逻辑。关键点是get_access_token()的缓存机制——飞书token有效期2小时,频繁刷新会触发频率限制。
    • get_messages()支持分页,自动循环直到获取所有消息。需要注意的是飞书API返回的是嵌套结构,要正确解析data.items。
    • start_time和end_time是Unix时间戳(秒),调用方需要自己做时间转换。
    第二部分:Prompt工程

    Prompt设计决定了Agent的输出质量。自动周报Agent的prompt需要包含三个部分:角色设定、输出格式约束、上下文信息。

    SYSTEM_PROMPT = """你是一个专业的周报助手,擅长从零散的工作记录中提炼出结构清晰、有价值的周报。

    ## 你的能力
    从对话式的工作记录中识别关键事件、项目进展、问题与解决方案
    按照公司标准周报格式输出(工作摘要、重点项目、问题与风险、下周计划)
    用专业但不晦涩的语言描述技术工作,让非技术人员也能理解

    ## 重要约束
    1. **不要编造**:如果工作记录中没有提到某件事,不要自行添加,只能基于已有信息总结
    2. **区分事实和推断**:对于你基于上下文推断的内容,用"(推测)"标注
    3. **量化优先**:有数字的地方用数字,例如"完成了3个功能开发"而不是"完成了多个功能"
    4. **突出价值**:不只是罗列做了什么,要说明这些工作的业务价值或技术价值

    ## 输出格式
    ```markdown
    # 周报 – {姓名} – {日期}

    ## 本周工作摘要
    23句话概括整体工作)

    ## 重点项目进展
    ### 项目1名称
    进展描述
    关键成果:xxx
    遇到的问题及解决方案:(如有)

    ## 问题与风险
    问题描述及影响
    应对措施

    ## 下周工作计划
    1. xxx
    2. xxx
    3. xxx

    “”"

    USER_PROMPT_TEMPLATE = “”"以下是{姓名}本周({start_date} 至 {end_date})的工作消息记录:


    {messages_text}

    请根据以上记录生成标准周报。如果消息内容太少或太杂乱,请先说明情况再尽力总结。
    “”"

    **Prompt设计要点:**
    – 系统Prompt中明确了"不要编造"——这是防止大模型幻觉的关键约束,实际生产中必须强调
    – 约束4"量化优先"很实用,但需要配合Few-shot examples效果更好
    – 用户PromptTemplate把消息文本直接注入,消息量较大时需要注意token开销

    #### 第三部分:Agent循环实现

    这是Agent的核心——一个能自主循环、反思、重试的智能循环体:

    ```python
    import re
    from enum import Enum
    from dataclasses import dataclass, field
    from typing import Callable

    class AgentStatus(Enum):
    """Agent执行状态"""
    PLANNING = "planning"
    EXECUTING = "executing"
    OBSERVING = "observing"
    REASONING = "reasoning"
    FINISHED = "finished"
    FAILED = "failed"

    @dataclass
    class AgentStep:
    """Agent执行步骤记录"""
    status: AgentStatus
    thought: str
    action: str | None = None
    action_input: dict | None = None
    observation: str | None = None
    error: str | None = None

    @dataclass
    class WeeklyReportAgent:
    """自动周报生成Agent"""

    llm_client: Callable[[str], str] # LLM调用接口,传入prompt返回response
    feishu_client: FeishuClient
    email_client: object # 简化的邮件客户端

    max_turns: int = 10 # 最多循环次数,防止无限循环
    max_retries: int = 3 # 单个工具调用的最大重试次数

    # Agent内部状态
    steps: list[AgentStep] = field(default_factory=list)
    memory: dict = field(default_factory=dict) # 情景记忆

    def run(self, user_id: str, start_time: int, end_time: int, recipient_email: str) -> str:
    """
    启动Agent执行流程

    参数:
    user_id: 飞书用户open_id
    start_time: 开始时间戳(秒)
    end_time: 结束时间戳(秒)
    recipient_email: 周报接收邮箱

    返回:
    生成的周报内容
    """
    self.steps = []
    self.memory = {
    "user_id": user_id,
    "start_time": start_time,
    "end_time": end_time,
    "recipient_email": recipient_email,
    "feishu_messages": [], # 存储获取到的飞书消息
    }

    for turn in range(self.max_turns):
    step = self._execute_turn(turn)
    self.steps.append(step)

    if step.status == AgentStatus.FINISHED:
    return step.observation or step.thought
    elif step.status == AgentStatus.FAILED:
    raise RuntimeError(f"Agent执行失败: {step.error}")

    # 超过最大循环次数,返回目前已收集到的信息
    return self._generate_fallback_report()

    def _execute_turn(self, turn: int) -> AgentStep:
    """执行一个循环周期"""
    current_plan = self._get_current_plan()

    # 阶段1: Reasoning – 思考当前状态
    thought = self._reason(current_plan)
    if "FINAL_ANSWER" in thought:
    # 已经准备好最终答案了
    return AgentStep(
    status=AgentStatus.FINISHED,
    thought=thought,
    observation=self._extract_final_answer(thought)
    )

    # 阶段2: Planning – 决定下一步行动
    action, action_input = self._decide_action(thought)

    # 阶段3: Executing – 执行工具调用
    observation, error = self._execute_action(action, action_input)

    # 阶段4: 更新记忆
    self.memory[f"step_{turn}_action"] = action
    self.memory[f"step_{turn}_result"] = observation

    return AgentStep(
    status=AgentStatus.REASONING,
    thought=thought,
    action=action,
    action_input=action_input,
    observation=observation,
    error=error
    )

    def _reason(self, current_plan: str) -> str:
    """调用LLM进行推理"""
    prompt = f"""当前任务:为用户生成自动周报

    当前状态:
    {current_plan}

    历史执行记录:
    {self._format_history()}

    请进行推理思考,说明当前状态和下一步计划。如果信息已经足够生成完整周报,请说明"FINAL_ANSWER"并给出最终周报内容。
    """
    response = self.llm_client(prompt)
    return response

    def _decide_action(self, thought: str) -> tuple[str, dict]:
    """从推理结果中提取行动"""
    # 简单实现:正则匹配 Action: xxx
    # 实际应该用更robust的解析方式
    action_match = re.search(r"Action:\\s*(\\w+)", thought)
    input_match = re.search(r"Action Input:\\s*(\\{.*?\\})", thought, re.DOTALL)

    if not action_match:
    return "finish", {}

    action = action_match.group(1)
    action_input = {}
    if input_match:
    try:
    action_input = json.loads(input_match.group(1))
    except json.JSONDecodeError:
    pass

    return action, action_input

    def _execute_action(self, action: str, action_input: dict) -> tuple[str, None] | tuple[None, str]:
    """执行具体的工具调用,带重试"""
    retry_count = 0
    last_error = None

    while retry_count < self.max_retries:
    try:
    if action == "get_feishu_messages":
    messages = self.feishu_client.get_messages(
    user_id=action_input.get("user_id"),
    start_time=action_input.get("start_time"),
    end_time=action_input.get("end_time"),
    )
    # 格式化消息为文本
    messages_text = self._format_messages(messages)
    return messages_text, None

    elif action == "generate_report":
    return self._generate_report_from_memory(), None

    elif action == "send_email":
    self.email_client.send(
    to=action_input.get("recipient"),
    subject=action_input.get("subject"),
    body=action_input.get("body"),
    )
    return f"邮件已发送到 {action_input.get('recipient')}", None

    elif action == "finish":
    return action_input.get("report", "任务完成"), None

    else:
    return f"未知动作: {action}", None

    except Exception as e:
    last_error = str(e)
    retry_count += 1

    return None, f"执行{action}失败,重试{self.max_retries}次后仍报错: {last_error}"

    def _generate_report_from_memory(self) -> str:
    """基于记忆中的消息生成周报"""
    messages = self.memory.get("feishu_messages", [])
    if not messages:
    return "错误:没有获取到工作消息,无法生成周报"

    # 组装prompt
    user_prompt = USER_PROMPT_TEMPLATE.format(
    name="员工",
    start_date="上周一",
    end_date="上周日",
    messages_text="\\n".join([f"- {m}" for m in messages])
    )

    full_prompt = SYSTEM_PROMPT + "\\n\\n" + user_prompt
    report = self.llm_client(full_prompt)
    return report

    def _get_current_plan(self) -> str:
    """生成当前计划状态描述"""
    has_messages = bool(self.memory.get("feishu_messages"))
    memory_summary = "\\n".join([
    f"- {k}: {v}" for k, v in self.memory.items()
    if k not in ["feishu_messages"]
    ])
    return f"""已知信息:
    – 是否已获取飞书消息:{has_messages}
    – 记忆中的其他信息:
    {memory_summary or '无'}
    """

    代码核心逻辑说明:

  • max_turns防死循环设计:每个Agent都要有最大循环次数限制,防止因为Prompt设计不当导致无限循环。这里设置10次,正常情况下一个周报生成不会超过3-4个步骤(获取消息→生成报告→发送邮件)。

  • 记忆系统:通过self.memory字典存储中间状态,这是Agent区别于单次LLM调用的关键。每个步骤的结果都写回记忆,后续步骤可以引用。

  • 重试机制:_execute_action中对每个工具调用都做了异常捕获和重试。这是工程化Agent的必备保障——网络不稳定、API临时故障都很常见,没有重试机制的Agent在生产环境中几乎不可用。

  • 状态机模式:AgentStatus枚举定义了Agent可能处于的状态,清晰的状态转移让debug变得容易。


  • 五、主流Agent框架横评

    目前市面上有多个Agent开发框架,我用过的主要有两个:LangGraph和CrewAI。还有一个较新的AutoGen(微软出品),适合复杂多智能体场景。

    下面是三个框架的横向对比:

    特性LangGraphCrewAIAutoGen
    设计理念 图结构定义工作流 角色扮演+任务协作 多智能体对话协作
    学习曲线 中等(需要理解图模型) 低(概念直观) 较高(设计复杂)
    适用场景 复杂条件分支、状态管理 多角色Agent协作 多轮对话、代码执行
    状态持久化 原生支持checkpoint 需自行实现 支持
    工具集成 LangChain生态 有限 有限
    生产成熟度 高(LangChain团队维护) 中(社区活跃) 中(微软实验性项目)
    国内适用性 好(LangChain中文文档丰富) 一般(英文为主) 一般

    我的推荐:

    • 如果你是个人开发者或小团队,做垂直场景的自动化,推荐用CrewAI,上手最快,概念直观,5分钟能跑通一个demo
    • 如果你要做复杂的企业级工作流,有多个系统要对接,推荐用LangGraph,它的状态管理和checkpoint能力是生产级别的
    • 如果你在研究多智能体协作的前沿课题,推荐用AutoGen,但要做好心理准备——它的设计初衷是研究不是生产

    六、Agent落地踩过的坑

    光讲架构和代码不够,我来说几个实际落地过程中踩过的坑,这些都是花时间换来的经验。

    坑1:Token窗口不够用

    有一次给一个销售团队做CRM摘要Agent,调用他们的CRM系统API拉取客户跟进记录。CRM返回的数据格式很松散,一条记录可能有50字备注,也可能有一篇500字的邮件往来历史。

    Prompt设计时我没有考虑周报生成和CRM数据注入之间的token竞争。结果:用户一周的销售跟进记录有3万多token,加上Prompt模板、周报格式要求,总token数超过了模型的上下文窗口限制,生成的内容永远是截断的。

    解决方案:在数据获取阶段就做预处理——提取关键字段(客户名、跟进时间、关键动作、金额),丢弃原始的长文本。把3万字原始数据压缩到2000字的结构化摘要。

    坑2:工具返回格式不稳定

    飞书的消息API返回格式在不同版本有差异。开发时用的是v1版本,返回结构是:

    {"code": 0, "data": {"items": []}}

    后来飞书升级了API,返回变成了:

    {"code": 0, "data": {"messages": {"list": []}}}

    Agent调用飞书工具时没有报错,因为它只负责调用,真正解析的是调用方代码。但解析失败了,Agent拿到了空数据,生成周报时告诉用户"本周没有工作记录"——用户在周会上社死了。

    解决方案:

  • 所有外部API调用必须做响应格式校验,不满足预期格式时抛异常而不是返回空数据
  • 对外部系统依赖要做集成测试,不能只测Agent的推理逻辑
  • 坑3:Agent"自信地犯错"

    大模型有一个特性:它会编造信息,而且编造的时候语气很自信。这在Agent场景下更危险——因为Agent会基于上一步的输出继续执行,如果上一步"自信地错了",后续步骤会把错误放大。

    有一次Agent在周报中生成了"本周完成了XXX项目的上线部署",实际上线部署是下周。原因是用户的消息记录里提到了项目进展,Agent从上下文推断"快完成了"就变成了"完成了"。

    解决方案:

  • Prompt中明确约束"不要推断,只描述事实"
  • 对关键信息输出做交叉验证(比如周报中提到"上线",去查部署平台是否有对应的上线记录)
  • 输出给用户之前,有人参与的审核环节

  • 七、从单Agent到多Agent协作

    当任务变得复杂,单个Agent的能力就不够用了。这时候需要引入多Agent协作架构。

    举一个实际场景:季度业务复盘Agent

    这个任务需要:

  • 从飞书获取季度会议纪要
  • 从CRM系统拉取销售数据
  • 从数据分析平台拉取业务指标
  • 综合以上信息生成复盘报告
  • 如果做成一个单一大Agent,Prompt会极其复杂,而且不同类型的信息处理逻辑不同(会议纪要需要NLP理解,CRM数据需要结构化解析,指标数据需要图表生成)。

    更好的做法是分而治之,用专门的Agent处理专门的任务:

    #mermaid-svg-WiaRatN5IL5glkHN{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WiaRatN5IL5glkHN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WiaRatN5IL5glkHN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WiaRatN5IL5glkHN .error-icon{fill:#552222;}#mermaid-svg-WiaRatN5IL5glkHN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WiaRatN5IL5glkHN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WiaRatN5IL5glkHN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WiaRatN5IL5glkHN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WiaRatN5IL5glkHN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WiaRatN5IL5glkHN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WiaRatN5IL5glkHN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WiaRatN5IL5glkHN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WiaRatN5IL5glkHN .marker.cross{stroke:#333333;}#mermaid-svg-WiaRatN5IL5glkHN svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WiaRatN5IL5glkHN p{margin:0;}#mermaid-svg-WiaRatN5IL5glkHN .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-WiaRatN5IL5glkHN .cluster-label text{fill:#333;}#mermaid-svg-WiaRatN5IL5glkHN .cluster-label span{color:#333;}#mermaid-svg-WiaRatN5IL5glkHN .cluster-label span p{background-color:transparent;}#mermaid-svg-WiaRatN5IL5glkHN .label text,#mermaid-svg-WiaRatN5IL5glkHN span{fill:#333;color:#333;}#mermaid-svg-WiaRatN5IL5glkHN .node rect,#mermaid-svg-WiaRatN5IL5glkHN .node circle,#mermaid-svg-WiaRatN5IL5glkHN .node ellipse,#mermaid-svg-WiaRatN5IL5glkHN .node polygon,#mermaid-svg-WiaRatN5IL5glkHN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WiaRatN5IL5glkHN .rough-node .label text,#mermaid-svg-WiaRatN5IL5glkHN .node .label text,#mermaid-svg-WiaRatN5IL5glkHN .image-shape .label,#mermaid-svg-WiaRatN5IL5glkHN .icon-shape .label{text-anchor:middle;}#mermaid-svg-WiaRatN5IL5glkHN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-WiaRatN5IL5glkHN .rough-node .label,#mermaid-svg-WiaRatN5IL5glkHN .node .label,#mermaid-svg-WiaRatN5IL5glkHN .image-shape .label,#mermaid-svg-WiaRatN5IL5glkHN .icon-shape .label{text-align:center;}#mermaid-svg-WiaRatN5IL5glkHN .node.clickable{cursor:pointer;}#mermaid-svg-WiaRatN5IL5glkHN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-WiaRatN5IL5glkHN .arrowheadPath{fill:#333333;}#mermaid-svg-WiaRatN5IL5glkHN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WiaRatN5IL5glkHN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WiaRatN5IL5glkHN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WiaRatN5IL5glkHN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-WiaRatN5IL5glkHN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WiaRatN5IL5glkHN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-WiaRatN5IL5glkHN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WiaRatN5IL5glkHN .cluster text{fill:#333;}#mermaid-svg-WiaRatN5IL5glkHN .cluster span{color:#333;}#mermaid-svg-WiaRatN5IL5glkHN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-WiaRatN5IL5glkHN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WiaRatN5IL5glkHN rect.text{fill:none;stroke-width:0;}#mermaid-svg-WiaRatN5IL5glkHN .icon-shape,#mermaid-svg-WiaRatN5IL5glkHN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WiaRatN5IL5glkHN .icon-shape p,#mermaid-svg-WiaRatN5IL5glkHN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-WiaRatN5IL5glkHN .icon-shape .label rect,#mermaid-svg-WiaRatN5IL5glkHN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WiaRatN5IL5glkHN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-WiaRatN5IL5glkHN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-WiaRatN5IL5glkHN :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    用户发起季度复盘任务

    协调者Agent

    会议纪要Agent

    销售数据Agent

    业务指标Agent

    会议要点总结

    销售数据分析

    指标解读

    综合报告Agent

    最终复盘报告

    协调者Agent负责接收任务、分发给专业Agent、汇总结果,不做具体的信息处理。专业Agent各司其职,最后由综合报告Agent做统一整合。

    这种架构的优势:

    • 可维护性:每个Agent职责单一,出问题容易定位
    • 可扩展性:需要新的数据源时,加一个专门的Agent就行
    • Token效率:小Agent处理小任务,Prompt更精简,不容易超窗口

    八、未来展望:Agent将如何改变我们的工作

    说了这么多技术细节,我想最后聊聊Agent对工作方式的影响。

    过去一年多,我看到的变化是:Agent正在从"增强个人效率"进化到"重新定义工作流程"。

    最开始,Agent是作为"第二大脑"存在的——帮个人处理信息、整理笔记、生成初稿。这还是人主导,AI辅助。

    现在,Agent开始承担完整的"工作单元"——自动处理重复性任务、独立对接多个系统、在一定范围内自主决策。一个好的Agent系统,可以把一个原本需要2人天/周的管理工作量,降低到0.5人天/周。

    未来,我判断会看到更多Agent原生的工作方式——不是给现有流程加AI,而是从一开始就按照Agent的能力边界来设计工作流程。就像当年从本地服务器迁移到云计算,不只是把服务器搬上去,而是重新思考什么叫"服务器"。

    对于技术从业者来说,这意味着两个方向的机会:

  • Agent开发者:理解Agent的架构原理、掌握工程化能力,能够把AI能力落地成可靠的自动化系统
  • Agent使用专家:理解AI的能力边界和不可靠性,能够设计有效的人机协作流程,让AI做它擅长的事,让人做人真正需要做的判断

  • 写在最后

    AI Agent不是一个神奇的黑科技,它是大模型能力从"对话"到"行动"的关键一步。理解它的核心原理,掌握工程化的实现方法,是未来几年技术从业者的核心竞争力。

    希望这篇文章能帮你建立对Agent的整体认知——不只是知道怎么用,还知道为什么这样设计、常见的坑在哪里、未来的方向可能是什么。

    如果有任何具体问题,欢迎在评论区交流。下一个要拆解的话题,你们想看什么?


    参考说明:因网络原因无法实时获取外部链接,以下为基于知识库整理的主要参考来源:

    • ReAct论文:Synergizing Reasoning and Acting in Language Models (Princeton/Google DeepMind, 2023)
    • LangGraph文档:https://langchain-ai.github.io/langgraph/
    • CrewAI文档:https://docs.crewai.com/
    • 飞书开放平台文档:https://open.feishu.cn/document/
    赞(0)
    未经允许不得转载:171主机测评 » 当AI开始替你上班:大模型Agent如何重塑工作流自动化
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址