从LangChain走到AutoGPT:AI Agent开发笔记

那时候 AI Agent 还没现在这么火,LangChain 也刚到 0.1 版本,我还在想这玩意儿是不是只是又一层封装。

“AI 但问题是,谁来写那个让 AI 懂你意思的助手?

为什么需要 Agent 框架

直接用 API 调模型确实简单,但要做点复杂的事情就不太够用。

你让模型"写一段 Python 代码读取本地文件并统计行数”,模型可以输出代码,但你得自己执行;你让它"查一下 GitHub 上 LangChain 的最新版本号",它也可以给你指令,但你得自己去 curl。

这就像你雇佣了一个很聪明的顾问,但他只能给你写备忘录,不能帮你拨电话、查资料、跑代码。

LangChain 的 Agent 框架解决的核心问题

LangChain 的 Agent 框架其实就是给模型装上了"手脚"和"记忆":

from langchain.tools import Tool
from langchain.agents import initialize_agent, AgentType
from langchain.chat_models import ChatOpenAI

# 定义工具
def get_github_latest_release(repo_name):
    import requests
    url = f"https://api.github.com/repos/{repo_name}/releases/latest"
    response = requests.get(url)
    return response.json().get('tag_name', 'Unknown')

tools = [
    Tool(
        name="GitHubLatestRelease",
        func=get_github_latest_release,
        description="获取 GitHub 仓库的最新版本号,输入格式为 'owner/repo'"
    ),
    Tool(
        name="Calculator",
        func=lambda x: eval(x),
        description="执行数学计算,输入表达式字符串"
    )
]

# 初始化 Agent
llm = ChatOpenAI(model="gpt-4", temperature=0)
agent = initialize_agent(
    tools=tools,
    llm=llm,
    agent=AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True
)

# 运行
result = agent.run("LangChain 的最新版本是多少?")
print(result)

这个例子里的关键点在于:

  • 工具定义:把外部能力包装成模型能调用的函数
  • 描述与输入:让模型知道每个工具是干什么的、需要什么输入
  • 推理链条:模型自己决定用哪些工具、怎么组合它们

第一次跑通这个流程时我挺兴奋的——模型确实能理解我的自然语言,然后自己规划任务、调用工具、整合结果。但很快我就踩到了坑。

第一次踩坑:工具描述的微妙平衡

最开始的坑出现在工具描述上。

我有个工具用来查询本地文件内容,描述写得大概是这样:

Tool(
    name="ReadFile",
    func=read_file,
    description="读取本地文件内容,输入文件路径"
)

结果模型开始幻觉——当我说"看看当前目录下有什么文件"时,模型会试图用这个工具,然后传入一些不存在的路径。或者当我说"分析一下这个 CSV 文件"时,模型会尝试读取一个它自己编造的文件名。

问题根源:描述太泛

模型把"读取本地文件内容"理解得太宽泛了,以为可以用它来做任何和文件相关的事情。

后来我把描述改得更精确:

Tool(
    name="ReadFile",
    func=read_file,
    description="读取指定的本地文件内容。必须提供完整、有效的文件路径。只能读取文本文件,不能浏览目录或执行文件操作。"
)

再配合一些系统提示词约束,模型的表现就稳定多了。

但这个坑让我意识到一个更深层的问题:工具描述本质上是在教模型这个工具的能力边界。写得太泛,模型会过度使用;写得太窄,模型又想不到用它。

从 LangChain 到 AutoGPT:自主规划的诱惑与陷阱

LangChain 的 Agent 框架解决了一次对话内的任务规划问题,但真正让我对 Agent 开发有了新理解的是 AutoGPT。

AutoGPT 的思路更狂野——你给它一个目标,它会自己把目标拆解成子任务,自己规划执行步骤,自己调用工具,自己检查进度,甚至自己决定什么时候停止。

一个真实的 AutoGPT 实验

我当时让 AutoGPT 做这样一件事:

“帮我创建一个简单的 Python Web 应用,包含首页、关于页面,并且部署到 Vercel”

AutoGPT 开始了它的自主旅程:

[THOUGHT] 我需要创建一个 Python Web 应用,这应该使用 Flask 或 FastAPI。我先选择 Flask,因为它更简单。

[REASONING] 目标是创建 Web 应用并部署到 Vercel。步骤应该是:
1. 创建项目结构
2. 编写应用代码
3. 配置 Vercel 部署
4. 测试部署结果

[PLAN]
- Task 1: 创建项目目录和基本文件结构
- Task 2: 编写 Flask 应用代码
- Task 3: 创建 requirements.txt 和 vercel.json
- Task 4: 初始化 Git 仓库并提交
- Task 5: 部署到 Vercel

[CRITICISM] 这个计划看起来合理,但我应该先检查本地是否已安装 Flask 和 Git。

[NEXT ACTION] Execute shell command: python3 -c "import flask; print(flask.__version__)"

看着它一步步执行,我一度觉得这玩意儿真的能自动化一切。但很快问题就来了。

踩坑记录:无限循环与资源耗尽

AutoGPT 在执行 Task 3 时遇到了一个版本冲突问题。它尝试了几次不同的解决方案,每次都失败了,然后它开始:

[THOUGHT] 上一次的依赖安装失败了,可能是因为版本冲突。我应该尝试指定具体版本。

[REASONING] 我可以尝试先卸载所有相关包,然后重新安装指定版本。

[PLAN]
- Task 3.1: 卸载当前依赖
- Task 3.2: 安装指定版本的 Flask
- Task 3.3: 验证安装结果

[NEXT ACTION] Execute shell command: pip uninstall flask -y

这一步成功了,但接下来的安装又失败了。然后它开始循环尝试不同的安装策略,每次都失败,每次都生成新的子任务计划。

几个循环后,AutoGPT 仍然没有放弃,它开始尝试一些越来越离谱的方案——比如"手动编译 Flask 源码"、“检查系统环境变量”、“尝试不同的 Python 版本”。

我眼睁睁看着它跑了 20 多分钟,消耗了大量的 API 调用次数,最后依然卡在依赖安装这个环节。

问题根源:缺乏合理的停止条件

AutoGPT 的"永不放弃"精神看起来很励志,但在实际操作中就是资源浪费。

它缺少一个合理的停止条件:

  • 多次失败同一个子任务后,是否应该请求人工干预?
  • 某个环节卡住超过一定时间后,是否应该暂停并报错?
  • 总共尝试多少次后应该承认无法完成?

我后来在代码里加了一些约束:

MAX_TASK_ATTEMPTS = 3
MAX_TOTAL_STEPS = 20
STEP_TIMEOUT_SECONDS = 60

class TaskExecutor:
    def __init__(self):
        self.task_attempts = {}
        self.total_steps = 0
        self.last_reset_time = time.time()

    def should_continue(self, task_id):
        # 单个任务尝试次数检查
        if self.task_attempts.get(task_id, 0) >= MAX_TASK_ATTEMPTS:
            return False, f"任务 {task_id} 已达到最大尝试次数 {MAX_TASK_ATTEMPTS}"

        # 总步骤数检查
        if self.total_steps >= MAX_TOTAL_STEPS:
            return False, f"已达到最大步骤数 {MAX_TOTAL_STEPS}"

        # 超时检查
        if time.time() - self.last_reset_time > STEP_TIMEOUT_SECONDS:
            return False, f"任务执行超时 ({STEP_TIMEOUT_SECONDS}秒)"

        return True, ""

这才勉强控制住 AutoGPT 的"永动机"倾向。

任务规划:从线性到分层

从 LangChain 和 AutoGPT 的实践中,我意识到任务规划是 Agent 开发的核心,也是最容易被低估的部分。

线性规划的局限性

最开始的 Agent 都是线性规划——模型看到任务,直接输出一系列步骤。这在简单场景下够用,但遇到复杂任务就撑不住了。

比如一个这样的任务:

“分析一个 CSV 文件,根据销售额排名生成可视化报表,然后通过邮件发送给指定团队”

线性规划可能会这样做:

1. 读取 CSV 文件
2. 计算销售额排名
3. 生成图表
4. 发送邮件

但如果第 2 步失败了怎么办?如果 CSV 文件格式不对怎么办?如果邮件发送失败了要不要重试?

分层规划的实践

后来我开始尝试分层规划的方法,把任务拆成三层:

class TaskPlanner:
    def plan(self, user_request):
        # 第一层:理解目标
        goal = self.understand_goal(user_request)

        # 第二层:拆分子任务
        subtasks = self.breakdown_goal(goal)

        # 第三层:为每个子任务规划具体步骤
        execution_plans = {}
        for subtask in subtasks:
            execution_plans[subtask.id] = self.plan_execution(subtask)

        return {
            'goal': goal,
            'subtasks': subtasks,
            'execution_plans': execution_plans,
            'dependencies': self.analyze_dependencies(subtasks)
        }

    def understand_goal(self, request):
        # 使用模型理解用户意图
        pass

    def breakdown_goal(self, goal):
        # 拆解目标为子任务,识别关键里程碑
        pass

    def plan_execution(self, subtask):
        # 为子任务规划执行步骤,包括备选方案
        pass

    def analyze_dependencies(self, subtasks):
        # 分析任务依赖关系
        pass

这种方法的好处是:

  • 可观察性:你知道模型在哪个层次上思考
  • 可干预:子任务失败时,可以局部调整而不影响整体
  • 可扩展:新类型任务只需要扩展对应的层次

一个真实的分层规划例子

我做过一个实际项目,是用 Agent 自动化一些运维任务。其中有个任务是"检查服务器健康状态并在异常时报警"。

分层规划后是这样的:

{
  "goal": "监控服务器健康状态,异常时自动报警",
  "subtasks": [
    {
      "id": "collect_metrics",
      "name": "收集服务器指标",
      "milestone": "所有指标收集完成",
      "execution_plan": {
        "primary": [
          {"action": "ssh", "target": "server1", "command": "top -b -n 1 | head -20"},
          {"action": "ssh", "target": "server2", "command": "top -b -n 1 | head -20"},
          {"action": "parse", "type": "system_metrics"}
        ],
        "fallback": [
          {"action": "http", "endpoint": "/metrics", "parser": "prometheus"}
        ]
      }
    },
    {
      "id": "analyze_health",
      "name": "分析健康状态",
      "milestone": "健康状态判断完成",
      "execution_plan": {
        "primary": [
          {"action": "threshold_check", "metrics": ["cpu", "memory", "disk"]}
        ],
        "fallback": [
          {"action": "ml_anomaly_detection", "model": "baseline_model"}
        ]
      }
    },
    {
      "id": "alert_if_needed",
      "name": "异常时报警",
      "milestone": "报警决策完成",
      "execution_plan": {
        "primary": [
          {"action": "check_threshold", "condition": "any_anomaly"},
          {"action": "send_alert", "channels": ["email", "slack"]}
        ]
      }
    }
  ],
  "dependencies": [
    {"from": "collect_metrics", "to": "analyze_health"},
    {"from": "analyze_health", "to": "alert_if_needed"}
  ]
}

这样的结构下,模型可以在每个层次上做决策,我们也可以在关键点插入人工审核或硬性规则。

工具调用:从"能调"到"善调"

工具调用是 Agent 的"手脚",但怎么用好手脚是门学问。

工具设计的三个坑

我踩过的第一个坑是工具粒度问题。

最开始我设计工具时喜欢把功能做得很细小:

tools = [
    Tool(name="ReadFile", func=read_file, description="读取文件"),
    Tool(name="WriteFile", func=write_file, description="写入文件"),
    Tool(name="DeleteFile", func=delete_file, description="删除文件"),
    Tool(name="MoveFile", func=move_file, description="移动文件"),
    Tool(name="CopyFile", func=copy_file, description="复制文件"),
    # ... 更多细碎工具
]

结果模型在规划任务时会过度纠结:它不确定该用哪个工具,或者会把简单的任务拆得很复杂。比如"把 A 文件的内容复制到 B 文件",模型可能会规划成"读取 A -> 临时存储 -> 创建 B -> 写入 B -> 删除临时存储"。

第二个坑是工具输入设计不当。

我有个工具用来执行 shell 命令,最开始是这样:

def execute_command(command):
    result = subprocess.run(command, shell=True, capture_output=True)
    return result.stdout.decode()

这给了模型太大的自由度——它可以执行任何命令。在一次测试中,模型居然尝试执行 rm -rf /(当然被我的沙箱拦截了)。

后来我改成了这样:

def execute_safe_command(command, allowed_commands):
    # 验证命令是否在允许列表中
    if not any(command.startswith(cmd) for cmd in allowed_commands):
        raise ValueError(f"命令 {command} 不在允许列表中")

    # 添加超时和资源限制
    result = subprocess.run(
        command.split(),
        timeout=30,
        capture_output=True,
        check=True
    )
    return result.stdout.decode()

第三个坑是工具输出格式不统一。

有些工具返回字符串,有些返回字典,有些返回列表。模型很难理解这些不同格式的输出,也很难把它们组合起来。

后来我统一了工具输出格式:

class ToolResult:
    def __init__(self, success, data, error=None):
        self.success = success
        self.data = data
        self.error = error

    def to_dict(self):
        return {
            'success': self.success,
            'data': self.data,
            'error': self.error
        }

def read_file(filepath):
    try:
        with open(filepath, 'r') as f:
            content = f.read()
        return ToolResult(True, {'content': content, 'lines': len(content.split('\n'))})
    except Exception as e:
        return ToolResult(False, None, error=str(e))

这样模型就能统一处理工具输出,也更容易判断调用是否成功。

工具组合的实践

最有意思的是工具组合。有些任务需要多个工具配合完成。

比如"分析一个项目的代码复杂度",需要:

# 工具1:扫描项目文件
scan_project(path) -> List[FileInfo]

# 工具2:读取文件内容
read_file(filepath) -> FileContent

# 工具3:计算复杂度
calculate_complexity(code) -> ComplexityMetrics

# 工具4:生成报告
generate_report(metrics) -> Report

在 LangChain 里,可以这样组合:

from langchain.chains import SequentialChain
from langchain.prompts import PromptTemplate

# 单个任务链
file_scanner_chain = LLMChain(
    llm=llm,
    prompt=PromptTemplate(
        input_variables=["project_path"],
        template="扫描项目 {project_path},找出所有 Python 文件。"
    )
)

complexity_analyzer_chain = LLMChain(
    llm=llm,
    prompt=PromptTemplate(
        input_variables=["file_content", "file_path"],
        template="分析文件 {file_path} 的代码复杂度:\n{file_content}\n\n给出圈复杂度、函数数量等指标。"
    )
)

report_generator_chain = LLMChain(
    llm=llm,
    prompt=PromptTemplate(
        input_variables=["all_complexity_data"],
        template="基于以下复杂度分析数据生成报告:\n{all_complexity_data}"
    )
)

# 组合链
full_chain = SequentialChain(
    chains=[file_scanner_chain, complexity_analyzer_chain, report_generator_chain],
    input_variables=["project_path"],
    output_variables=["report"]
)

result = full_chain.run(project_path="/path/to/project")

但这种组合方式还是太死板,实际场景下往往需要更灵活的工具组合策略。

记忆管理:短期、长期和上下文窗口

Agent 记忆是个微妙的问题——太少记不住,太多又撑爆上下文窗口。

三种记忆的实践

我从 LangChain 学到了三种记忆:

短期记忆:当前对话内的上下文。这在 LangChain 里是自动管理的,通过对话历史缓冲区实现。

长期记忆:跨对话的持久化记忆。我主要用向量数据库存储:

from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
from langchain.memory import VectorStoreRetrieverMemory

# 初始化向量存储
vectorstore = Chroma(
    collection_name="agent_memories",
    embedding_function=OpenAIEmbeddings()
)

# 配置记忆检索
retriever = vectorstore.as_retriever(
    search_kwargs={"k": 3}  # 每次检索最相关的 3 条
)

memory = VectorStoreRetrieverMemory(
    retriever=retriever,
    memory_key="relevant_memories"
)

# 保存记忆
memory.save_context(
    {"input": "用户偏好:喜欢简洁的代码风格"},
    {"output": "已记录用户代码风格偏好"}
)

# 检索记忆
relevant_memories = memory.load_memory_variables({"query": "代码风格"})["relevant_memories"]

上下文记忆:当前任务的临时状态。这个我通常用简单的 Python 对象管理:

class TaskContext:
    def __init__(self):
        self.variables = {}
        self.tool_results = {}
        self.decisions = []

    def set(self, key, value):
        self.variables[key] = value

    def get(self, key, default=None):
        return self.variables.get(key, default)

    def record_tool_result(self, tool_name, result):
        self.tool_results[tool_name] = result

    def record_decision(self, decision):
        self.decisions.append({
            'timestamp': datetime.now(),
            'decision': decision
        })

上下文窗口的真实挑战

模型上下文窗口有限,这是我踩过的最大坑之一。

在一次复杂任务中,Agent 需要处理多个大文件,每个文件都有几千行代码。LangChain 的默认记忆管理会把所有工具调用历史和结果都塞进上下文,结果很快就爆了上下文窗口。

后来我做了几个优化:

class ContextAwareMemory:
    def __init__(self, max_tokens=4000):
        self.max_tokens = max_tokens
        self.conversation_history = []
        self.important_memories = []

    def add_message(self, role, content, importance=1.0):
        message = {'role': role, 'content': content, 'importance': importance}
        self.conversation_history.append(message)
        self._prune_if_needed()

    def _prune_if_needed(self):
        current_tokens = sum(len(msg['content']) for msg in self.conversation_history)
        if current_tokens > self.max_tokens:
            # 按重要性排序,移除低重要性的消息
            sorted_messages = sorted(
                self.conversation_history,
                key=lambda x: x['importance']
            )
            # 保留最重要的 80%
            keep_count = int(len(sorted_messages) * 0.8)
            self.conversation_history = sorted_messages[-keep_count:]

    def get_context(self):
        return self.conversation_history

另一个思路是压缩历史:

def compress_conversation(messages):
    # 把连续的工具调用压缩成摘要
    compressed = []
    i = 0
    while i < len(messages):
        if messages[i]['role'] == 'assistant' and i + 2 < len(messages):
            if messages[i + 1]['role'] == 'tool' and messages[i + 2]['role'] == 'assistant':
                # 压缩工具调用链
                tool_call = messages[i]['content']
                tool_result = messages[i + 1]['content']
                assistant_response = messages[i + 2]['content']
                compressed.append({
                    'role': 'assistant',
                    'content': f"[工具调用: {tool_call} -> 结果: {tool_result[:100]}... -> 回应: {assistant_response[:100]}...]"
                })
                i += 3
                continue
        compressed.append(messages[i])
        i += 1
    return compressed

错误处理:让 Agent 知道什么时候该求助

Agent 最大的问题不是不会做,而是不知道自己不会做。

常见的错误类型

我总结了几种常见错误:

工具调用错误:工具执行失败(文件不存在、权限不足、网络问题等)

def safe_tool_call(tool_name, tool_func, **kwargs):
    try:
        result = tool_func(**kwargs)
        if not result.success:
            return {
                'status': 'error',
                'error_type': 'tool_execution_error',
                'message': f"工具 {tool_name} 执行失败: {result.error}"
            }
        return {
            'status': 'success',
            'data': result.data
        }
    except Exception as e:
        return {
            'status': 'error',
            'error_type': 'unexpected_error',
            'message': f"工具 {tool_name} 遇到意外错误: {str(e)}"
        }

规划错误:任务规划不合理或遗漏关键步骤

def validate_plan(plan):
    validation_results = []

    # 检查依赖关系
    dependencies = plan.get('dependencies', [])
    for dep in dependencies:
        if dep['from'] not in plan['subtasks'] or dep['to'] not in plan['subtasks']:
            validation_results.append({
                'severity': 'error',
                'message': f"依赖关系引用了不存在的子任务: {dep}"
            })

    # 检查循环依赖
    if has_circular_dependencies(dependencies):
        validation_results.append({
            'severity': 'error',
            'message': "检测到循环依赖"
        })

    return validation_results

状态不一致错误:执行过程中状态不匹配

class StateValidator:
    def __init__(self):
        self.expected_states = {}

    def define_transition(self, from_state, to_state, condition):
        if from_state not in self.expected_states:
            self.expected_states[from_state] = []
        self.expected_states[from_state].append({
            'to': to_state,
            'condition': condition
        })

    def validate_transition(self, from_state, to_state, context):
        if from_state not in self.expected_states:
            return False, f"未知的状态: {from_state}"

        valid_transitions = self.expected_states[from_state]
        for transition in valid_transitions:
            if transition['to'] == to_state:
                if eval(transition['condition'], {}, context):
                    return True, ""
                else:
                    return False, f"状态转换条件不满足: {transition['condition']}"

        return False, f"不允许的状态转换: {from_state} -> {to_state}"

人工干预策略

有些错误是模型无法自己解决的,这时候就需要人工干预:

class HumanInterventionHandler:
    def __init__(self, notification_channel="slack"):
        self.notification_channel = notification_channel
        self.intervention_threshold = 3  # 同类错误 3 次后请求人工干预

    def handle_error(self, error_type, error_context, error_count):
        if error_count >= self.intervention_threshold:
            return self.request_human_help(error_type, error_context)
        else:
            return self.suggest_recovery(error_type, error_context)

    def request_human_help(self, error_type, error_context):
        message = f"Agent 遇到持续错误,需要人工干预:\n\n"
        message += f"错误类型: {error_type}\n"
        message += f"错误上下文: {json.dumps(error_context, indent=2)}\n"
        message += f"\n请提供指导或批准跳过此步骤。"

        # 发送通知(这里简化为打印)
        print(f"[{self.notification_channel}] {message}")

        return {
            'action': 'wait_for_human',
            'message': '已请求人工干预,等待响应...'
        }

    def suggest_recovery(self, error_type, error_context):
        recovery_strategies = {
            'tool_execution_error': [
                '检查工具参数是否正确',
                '验证工具依赖环境是否正常',
                '尝试使用备用工具'
            ],
            'planning_error': [
                '重新评估任务目标',
                '增加子任务的颗粒度',
                '补充必要的任务步骤'
            ]
        }

        strategies = recovery_strategies.get(error_type, ['重新尝试', '联系技术支持'])

        return {
            'action': 'retry_with_suggestion',
            'suggestions': strategies
        }

实战案例:从零构建一个实用 Agent

说这么多,不如看个真实案例。

项目背景

我之前在公司内部做了一个"自动化运维 Agent",主要任务是:

  • 监控服务器资源使用情况
  • 自动执行常见运维操作(重启服务、清理日志、备份等)
  • 异常时报警并给出初步诊断

架构设计

整体架构分三层:

graph TB A[用户请求] --> B[意图理解层] B --> C[任务规划层] C --> D[工具执行层] D --> E[结果整合层] E --> F[响应生成] C --> G[记忆管理] G --> C D --> H[错误处理] H --> C

核心代码

class OpsAgent:
    def __init__(self, config):
        self.llm = ChatOpenAI(model="gpt-4", temperature=0)
        self.tools = self._init_tools(config)
        self.memory = ContextAwareMemory(max_tokens=3000)
        self.state_validator = StateValidator()
        self.intervention_handler = HumanInterventionHandler()

    def _init_tools(self, config):
        return {
            'check_server_health': Tool(
                name="CheckServerHealth",
                func=self._check_server_health,
                description="检查指定服务器的健康状态,包括 CPU、内存、磁盘使用率"
            ),
            'restart_service': Tool(
                name="RestartService",
                func=self._restart_service,
                description="重启指定的服务,需要服务名称和目标服务器"
            ),
            'clean_logs': Tool(
                name="CleanLogs",
                func=self._clean_logs,
                description="清理指定服务器的日志文件,超过指定天数的日志会被删除"
            ),
            'backup_data': Tool(
                name="BackupData",
                func=self._backup_data,
                description="备份指定数据到远程存储"
            ),
            'send_alert': Tool(
                name="SendAlert",
                func=self._send_alert,
                description="发送报警消息到指定渠道(邮件、Slack、企业微信等)"
            )
        }

    async def process_request(self, user_request):
        # 1. 理解意图
        intent = await self._understand_intent(user_request)
        self.memory.add_message('user', user_request, importance=1.0)

        # 2. 规划任务
        plan = await self._plan_tasks(intent)
        validation = validate_plan(plan)
        if validation:
            return {
                'status': 'error',
                'message': '任务规划验证失败',
                'details': validation
            }

        # 3. 执行任务
        execution_result = await self._execute_plan(plan)

        # 4. 整合结果
        response = await self._generate_response(execution_result)

        return response

    async def _understand_intent(self, user_request):
        prompt = f"""
        分析以下用户请求,提取关键信息:
        请求: {user_request}

        请提取:
        1. 意图类型(监控/操作/查询)
        2. 目标对象(服务器/服务/数据)
        3. 具体参数
        4. 优先级
        """

        response = await self.llm.ainvoke(prompt)
        return parse_intent_response(response.content)

    async def _plan_tasks(self, intent):
        prompt = f"""
        基于以下意图,规划详细的执行任务:
        {intent.to_dict()}

        可用工具:
        {self._get_tool_descriptions()}

        请生成任务计划,包括:
        1. 子任务列表
        2. 执行顺序
        3. 依赖关系
        4. 每个步骤的预期结果
        """

        response = await self.llm.ainvoke(prompt)
        return parse_task_plan(response.content)

    async def _execute_plan(self, plan):
        results = []
        error_count = {}

        for step in plan['steps']:
            tool_name = step['tool']
            tool_args = step['args']

            # 执行工具
            result = await safe_tool_call(
                tool_name,
                self.tools[tool_name].func,
                **tool_args
            )

            # 错误处理
            if result['status'] == 'error':
                error_key = f"{tool_name}_{result['error_type']}"
                error_count[error_key] = error_count.get(error_key, 0) + 1

                recovery = self.intervention_handler.handle_error(
                    result['error_type'],
                    result,
                    error_count[error_key]
                )

                if recovery['action'] == 'wait_for_human':
                    return {
                        'status': 'paused',
                        'message': '等待人工干预',
                        'recovery': recovery
                    }
                elif recovery['action'] == 'retry_with_suggestion':
                    # 应用建议后重试
                    step['args'] = apply_suggestion(step['args'], recovery['suggestions'])
                    continue

            results.append({
                'step': step,
                'result': result
            })

            self.memory.add_message(
                'assistant',
                f"执行 {tool_name} 完成",
                importance=0.8
            )

        return {
            'status': 'completed',
            'results': results
        }

实际效果

这个 Agent 在实际运行中的表现还算稳定,但也有一些有趣的观察:

  • 简单任务(比如"检查服务器 A 的状态")执行效果很好,准确率在 95% 以上
  • 中等复杂度任务(比如"重启所有后端服务并发送通知")成功率在 80% 左右,主要失败原因是一些边缘情况
  • 复杂任务(比如"诊断性能问题并给出优化建议")成功率只有 60%,经常需要人工干预

最有意思的是,Agent 在执行失败时给出的"诊断"往往比预期的要准确——它能够从错误信息中提取有用的线索,甚至能识别出一些我们没考虑过的问题。

经验总结:从失败中学到的东西

折腾了一圈 AI Agent 开发,我有几个比较实在的体会。

1. 不要高估模型的自主能力

AutoGPT 那套"完全自主"的思路在理论上很美好,但在实际应用中,给模型太多自由度反而会降低可靠性

适度的约束和人工干预是必要的。特别是在生产环境中,安全性比自主性更重要。

2. 工具设计比模型能力更重要

我花了很多时间优化 prompt 和模型参数,但后来发现,工具设计的质量对最终效果的影响可能更大

  • 工具描述要精确但不过度限制
  • 工具输入要做严格验证
  • 工具输出要统一格式
  • 工具粒度要适中——太细增加决策复杂度,太粗减少灵活性

3. 任务规划是核心能力

Agent 的智能程度主要体现在任务规划上,而不是单个工具的执行上。

好的任务规划应该具备

  • 合理的任务拆解
  • 明确的依赖关系
  • 可验证的中间状态
  • 灵活的回滚机制

4. 错误处理要主动

不要等错误发生后再处理,要在规划阶段就预想可能的错误并准备应对策略。

主动错误处理的要点

  • 预定义常见错误场景
  • 设计多层次的恢复机制
  • 设置合理的重试阈值
  • 保留人工干预的接口

5. 记忆管理要精简

模型上下文窗口有限,记忆管理不能简单粗暴。

精简记忆的策略

  • 区分长期记忆和短期记忆
  • 对记忆内容做重要性标注
  • 定期压缩历史对话
  • 只保留与当前任务相关的记忆

现状与未来

现在 Agent 开发已经从"玩具阶段"进入了"实用阶段",但距离"完全可靠"还有很长的路。

当前主要挑战

  • 可靠性:复杂任务的执行成功率仍然不够稳定
  • 可解释性:很多时候我们不知道 Agent 为什么做出某个决策
  • 安全性:工具调用和任务执行的安全边界难以完全控制
  • 成本控制:复杂任务的 API 调用成本可能很高

值得关注的趋势

  • 更专业的 Agent 框架:像 LangGraph 这样的框架提供了更好的任务规划能力
  • 更强大的记忆机制:向量数据库 + 结构化存储的组合正在成为标准
  • 更好的工具生态:越来越多的工具开始提供 Agent 友好的接口
  • 混合架构:规则引擎 + LLM 的混合架构在可靠性上更有优势

最后的一些思考

从 LangChain 到 AutoGPT,再到自己魔改的方案,这条路走得不算顺利,但每踩一个坑都让我对 Agent 开发有了更深的理解。

Agent 开发不是在造一个"能代替人"的系统,而是在造一个"能辅助人"的系统。它的价值不在于完全自动化,而在于把那些重复的、机械的任务自动化,让人能专注于需要判断和决策的部分。

如果你也在折腾 Agent 开发,我的建议是:

  • 从小处开始,先把一个简单场景做扎实
  • 不要追求"完全自主",适度的约束和人工干预是正常的
  • 工具设计和任务规划比模型微调更重要
  • 把重点放在可靠性和可观测性上,而不是炫酷的功能

Agent 开发还处在早期阶段,很多问题还没有标准答案。但这恰恰是有意思的地方——我们在和一个新兴的技术一起成长,每一次失败都可能成为下一个突破的起点。

只要别让 Agent 把你的服务器删了就行。

版权声明: 本文首发于 指尖魔法屋-从LangChain走到AutoGPT:AI Agent开发笔记https://blog.thinkmoon.cn/post/142-ai-agent-practice-langchain-autogpt/) 转载或引用必须申明原指尖魔法屋来源及源地址!