尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

构建可中断恢复的AI Agent系统:人机协作架构与实战指南

构建可中断恢复的AI Agent系统:人机协作架构与实战指南 1. 从“单打独斗”到“团队协作”AI Agent的范式转变最近在折腾AI应用开发的朋友可能都绕不开一个词Agent。从年初的AutoGPT爆火到后来各种“自主智能体”框架层出不穷大家似乎都在追求一个目标——让AI自己动起来完成一个复杂的、多步骤的任务。我一开始也是这个思路的拥趸花了不少时间研究如何让一个Agent“全自动”地规划、执行、反思试图打造一个不知疲倦的“数字员工”。但踩过几个坑之后我发现事情没那么简单。让AI完全自主就像让一个刚入职的新人独立负责一个跨部门大项目结果往往是灾难性的它可能会陷入死循环在一个无关紧要的细节上钻牛角尖可能会因为工具调用失败而“卡死”不知道如何回退更常见的是它执行到一半你发现方向偏了却很难中途介入纠正只能眼睁睁看着它跑偏或者干脆重启整个流程。这种“黑盒式”的自动化在演示时很酷但在真实、严肃的业务场景中可靠性和可控性都大打折扣。于是我的思路开始转变。与其追求不切实际的“完全自主”不如回归一个更务实、更强大的模式人机协作。我们搭建的AI Agent不应该是一个取代人类的“终结者”而应该是一个能力超强的“副驾驶”或“智能助手”。它的核心价值不在于“替代”而在于“增强”——它能以惊人的速度处理信息、调用工具、生成草稿而人类则负责提供高层意图、进行关键决策、纠正方向偏差。这种协作模式才是当前技术条件下最具生产力和实用价值的路径。而要实现有效的人机协作一个无法回避的核心技术挑战就是中断与恢复。想象一下你和助手正在合作写一份报告你临时接到一个电话离开了半小时。回来后你希望助手能清晰地告诉你“您离开前我们刚完成了市场分析部分的第一小节正在搜集竞争对手的数据但遇到了XX网站访问限制的问题。这是目前的草稿您看接下来是优先解决访问问题还是跳过这部分先写结论”——这才是理想的协作体验。我们的AI Agent也必须具备类似的能力在长时间、多步骤的任务中能够优雅地暂停中断完整地保存当前状态上下文、历史、工具调用结果、临时变量并在需要时准确地从断点继续恢复。这不仅是技术实现更是设计理念的体现。所以今天我想分享的就是如何从零开始搭建一个以人机协作为设计核心、具备强大中断恢复能力的AI Agent系统。我们会从最根本的架构设计聊起一步步拆解状态管理、对话编排、持久化存储等关键环节并分享我在实现过程中趟过的那些坑和总结出的实用技巧。你会发现一个“可中断、可恢复”的Agent其代码结构和思维模型与一个“一镜到底”的Agent有着本质的不同。2. 架构基石设计一个支持协作与状态管理的Agent核心要支持人机协作和中断恢复我们首先得抛弃那种“一次性流水线”的Agent设计。传统的简单Agent可能就是一个循环接收用户输入 - LLM思考 - 执行动作 - 更新记忆 - 循环。这种设计里状态是临时的任务是不可中断的。我们需要的是一个更具弹性的架构。我把这个核心架构称为“状态驱动的协作式Agent”。它的核心组件包括Agent 大脑 (Brain)通常由大语言模型LLM担任负责理解意图、规划步骤、决定何时调用工具或请求人工输入。对话与任务管理器 (Orchestrator)这是整个系统的调度中心。它管理一个或多个任务Session的生命周期维护任务状态机如运行中、等待用户输入、已暂停、已完成、已失败并负责协调“大脑”与下方各个模块的交互。状态存储器 (State Store)这是实现中断恢复的关键。它需要持久化保存任务的所有上下文信息。这不仅仅是聊天历史而是一个结构化的“任务快照”。工具集 (Toolkit)Agent可以调用的外部能力如搜索网络、查询数据库、执行代码、调用API等。人机交互接口 (Human-in-the-loop Interface)提供明确的机制让Agent可以主动向用户提问、确认也让用户可以随时中断、查看状态、修改指令或提供额外信息。其中状态存储器的设计是重中之重。我们需要保存哪些状态才能让Agent在几天后恢复时还能无缝衔接至少需要以下几类会话元数据 (Session Metadata)会话ID、创建时间、最后活跃时间、当前状态运行/暂停、所属用户等。任务目标与参数 (Goal Parameters)用户最初的任务描述以及任何解析出来的结构化参数。这是恢复后不会迷失方向的“北极星”。完整的对话历史 (Full Dialogue History)包括用户消息、Agent的思考过程、工具调用请求、工具执行结果、以及系统提示词。注意这里要保存的是原始记录而不是经过总结的“记忆”。工具调用上下文 (Tool Call Context)当前或最近一次工具调用的详细信息包括函数名、参数、执行结果、成功或错误状态。工作内存/暂存器 (Working Memory)任务执行过程中产生的中间结果、临时变量或结构化数据。例如在撰写报告时已经收集到的数据点列表在编写代码时已经定义好的函数接口。执行计划与进度 (Plan Progress)如果Agent进行了任务分解那么当前的计划树、哪些子任务已完成、哪些正在进行、哪些尚未开始这些信息都需要保存。在技术选型上对于原型或轻量级应用你可以使用SQLite或本地JSON文件来存储状态。但对于需要高可靠性和并发访问的生产环境我强烈推荐使用像Redis用于快速缓存和会话状态配合PostgreSQL或MongoDB用于持久化存储和复杂查询的组合。Redis的键过期和数据结构如Hash, List非常适合管理活跃会话的实时状态而关系型或文档型数据库则用于长期归档和复杂的状态查询。一个常见的误区是只保存对话历史。当任务复杂、涉及多轮工具调用和中间状态时仅凭对话历史很难精准恢复。比如Agent调用了一个返回大量数据的搜索API并在后续思考中引用了其中的第5条结果。如果只保存历史恢复时你需要重新解析整个冗长的结果。而如果我们在工作内存中明确保存了“search_results[4] {title: ‘…’ url: ‘…’}”恢复就会高效且准确得多。3. 实现中断如何让Agent优雅地“按下暂停键”有了支持状态保存的架构接下来我们要设计中断机制。中断不是简单的CtrlC杀死进程而是一个受控的状态转换过程。中断的触发可以由用户主动发起也可以由Agent在特定条件下自动请求。3.1 用户主动中断这是最常见的场景。用户可能想中途修改需求、补充信息或者只是暂时离开。我们需要在交互界面上提供一个清晰的“暂停”或“保存并退出”按钮。当这个信号发出时Orchestrator任务管理器需要执行以下流程完成当前原子操作确保当前正在执行的最小单位任务完成。例如如果正在调用一个写数据库的API必须等待这个调用完成并收到响应避免留下半截子事务。捕获完整状态调用State Store将当前会话的所有状态如上一节所述序列化并持久化。这里的关键是一致性快照。你需要确保保存的状态是某个瞬间的完整视图而不是正在变化过程中的碎片。更新会话状态将会话状态标记为“PAUSED_BY_USER”或类似的枚举值。这有助于区分是因为错误暂停还是主动暂停。清理与确认释放可能占用的临时资源如打开的文件句柄、网络连接池并向用户返回一个明确的标识符如session_id并告知“任务已暂停状态已保存。您可以使用会话IDabc123随时恢复”。3.2 Agent请求中断等待人工输入这是人机协作的精髓。Agent在运行中遇到无法自主决策、需要权限确认或信息补充时应主动“中断”自己向用户发起询问。例如“我已经分析了前三个季度的销售数据发现Q3的增长率异常。我需要访问财务系统的原始交易日志进行深度归因。请问您是否授权我调用‘get_raw_transaction_logs’这个工具【是/否】” “我正在为您起草项目计划书已经列出了主要阶段。关于‘技术选型评估’这个阶段您希望我侧重于对比哪些维度的指标例如性能、成本、社区活跃度、学习曲线”实现这种中断需要在Agent的“大脑”LLM的提示词Prompt工程中下功夫。我们要明确告诉LLM在遇到某些情况时不要自己猜测而是必须停止执行输出一个特定的结构化请求。例如在你的系统提示词中可以加入这样的指令你是一个协作式AI助手。当遇到以下情况时你必须暂停执行并严格按格式输出 1. 需要执行具有潜在风险或需要权限的操作如删除文件、发送邮件、调用付费API。 2. 任务描述中存在模糊、二义性或信息缺失足以影响后续关键决策。 3. 你产生了多个可行的后续方案需要用户选择方向。 输出格式必须是严格的JSON { “action”: “request_human_input”, “question”: “向用户提出的清晰问题”, “context”: “当前决策相关的背景信息”, “options”: [“可选选项A” “可选选项B”] // 可选 }然后Orchestrator在解析到LLM输出这样的JSON后就会将会话状态置为“AWAITING_HUMAN_RESPONSE”并将这个请求通过接口推送给前端界面等待用户回复。用户的回复会被作为新的输入连同保存的完整状态一起喂给LLM让任务继续。3.3 实现中的坑与技巧状态序列化的陷阱直接使用Python的pickle来序列化复杂的Agent对象尤其是那些包含了LLM客户端、网络会话等不可序列化资源的对象会失败。正确的做法是设计一个纯数据的状态对象Data Class只包含可序列化的基本类型str, int, dict, list等、字典和列表。在保存时将Agent运行时的内存状态“脱水”到这个数据对象中在恢复时再根据这个数据对象“水合”出一个新的Agent实例。这类似于Web开发中的serialize和deserialize。工具调用的原子性确保工具调用是“幕等”的或者至少在中断点附近是安全的。例如一个“发送邮件”的工具如果在“准备发送”状态被中断恢复后不应该重复发送。这通常需要在工具设计层面考虑比如生成一个唯一的操作ID并在执行前检查状态。给中断点加“书签”在保存状态时除了全局状态还可以显式地记录“中断原因”和“预期恢复点”。例如“pause_reason”: “NEED_PERMISSION_FOR_TOOL_X” “resume_step”: “call_tool_X_with_user_provided_params”。这样恢复逻辑可以更精准。4. 实现恢复从任意断点“无缝”接管的艺术恢复机制是中断机制的逆过程但更复杂因为它需要处理“时间流逝”带来的潜在问题。一个健壮的恢复流程不仅仅是加载状态那么简单。4.1 恢复流程的步骤会话查找与验证用户提供session_id系统首先在State Store中查找。检查会话是否存在、状态是否为可恢复的如PAUSED,AWAITING_INPUT并检查是否过期可根据业务设置TTL。状态反序列化与水合从数据库或文件中加载序列化的状态数据并将其“水合”成运行时的内存对象。重建Agent的“大脑”LLM客户端可能需要重新初始化但提示词和历史已包含在状态中、工具集绑定等。上下文重建与注入这是最关键的一步。你需要将加载的完整对话历史、工作内存等重新构建成LLM能够理解的上下文。对于大多数LLM API这意味着要将历史消息重新组装成合适的messages数组包括system,user,assistant角色并确保顺序和内容完整无损。同时那些保存在工作内存中的中间结果可能需要以某种方式“提醒”给LLM例如在系统提示词中追加一段“以下是任务暂停前已获取的信息{{working_memory}}”。决定恢复策略继续执行如果中断是简单的暂停那么直接让Agent从它上次停止的“思考环节”继续即可。LLM会根据完整的上下文自然地接上后续步骤。处理待决请求如果中断是因为Agent在等待用户输入AWAITING_HUMAN_RESPONSE那么恢复流程需要将用户新提供的答复作为一条新的user消息插入到历史上下文的末尾然后触发Agent继续处理。重新评估在某些场景下中断时间可能很长外部环境已变例如恢复时发现一个之前可用的API现在不可用了。更稳健的设计是在恢复时让Agent先做一个简短的“状态自检”输出如“我已恢复。在暂停前我们正在执行XX已完成YY下一步计划是ZZ。外部环境是否有变化需要我知晓”。这可以通过在恢复后的第一条系统提示词中增加指令来实现。继续执行与状态更新恢复后的Agent开始运行其产生的新的状态变化新的对话、工具调用、工作内存更新需要被实时地、增量地同步回State Store确保即使再次中断状态也是最新的。4.2 恢复时的挑战与解决方案上下文长度限制这是使用LLM时最现实的挑战。一个运行了上百轮对话、包含大量工具输出结果的复杂任务其完整历史很可能超出模型的上下文窗口。简单的截断只保留最近N条会导致遗忘早期关键信息。解决方案1分层记忆系统。维护一个“核心记忆”或“摘要记忆”。在每次保存状态前或者当历史达到一定长度时让LLM对之前的对话和结果进行一次增量式摘要提炼出对完成最终目标至关重要的“事实”和“决策”。将这份摘要作为系统提示词的一部分而原始详细历史则可以存档或丢弃。恢复时加载的是“摘要”“近期详细历史”。解决方案2向量化检索。将所有历史对话和工具结果块chunk都存入向量数据库。恢复时根据当前任务目标和最近几步去向量库中检索最相关的历史片段动态地构建上下文。这更灵活但实现复杂度更高。工具状态过期中断期间外部工具或数据源可能已发生变化。例如一个查询股票价格的工具恢复后价格早已更新。解决方案对于对实时性敏感的工具调用在恢复后的首次相关操作中设计一个“验证”或“刷新”机制。或者在Agent的提示词中强调对于时间敏感的信息应以恢复后的重新查询为准。“失忆”与逻辑连贯性即使恢复了全部文本历史LLM有时也可能在逻辑衔接上出现轻微“断层”尤其是当任务规划非常复杂时。解决方案在恢复后的第一条助理消息中可以“强迫”Agent先输出一个简短的回顾。在提示词中设计“首先请简要复述我们暂停前正在进行的任务、已完成的关键步骤以及下一步计划。确认无误后我们再继续。” 这相当于让LLM自己做一个上下文加载正确性的校验。5. 实战演练构建一个支持协作的网页爬取与分析Agent光讲理论有点抽象我们用一个具体的例子来串起所有概念构建一个网页爬取与分析Agent。它的任务是用户给定一个主题比如“大语言模型推理优化最新进展”它能自动搜索、爬取相关文章提取核心内容并生成一份摘要报告。在这个过程中用户可能需要介入比如确认要爬取的网站列表、在遇到反爬机制时决定策略、或者对摘要的侧重点提出要求。5.1 系统组件设计Agent Brain使用GPT-4或Claude 3等高性能LLM负责规划步骤如“先搜索关键词 - 筛选前10个结果 - 逐个爬取 - 解析内容 - 总结”决定何时调用工具。Orchestrator用FastAPI或类似框架构建一个Web服务管理会话。每个/start_task请求创建一个新会话。State StoreRedis存储活跃会话的实时状态session_id - 状态哈希包括当前步骤、临时数据、锁等。设置30分钟过期时间用于会话保持。PostgreSQL存储完整的、结构化的会话状态快照。表结构设计如下CREATE TABLE agent_sessions ( id VARCHAR(64) PRIMARY KEY, user_id VARCHAR(64), goal TEXT, status VARCHAR(32), -- ‘RUNNING’ ‘PAUSED’ ‘AWAITING_INPUT’ ‘COMPLETED’ ‘FAILED’ current_step VARCHAR(255), serialized_state JSONB, -- 存储完整的对话历史、工作内存等 created_at TIMESTAMP, updated_at TIMESTAMP );Toolkitweb_search(query: str) - List[SearchResult]: 调用SerpAPI或SearXNG进行搜索。fetch_webpage(url: str) - str: 使用requests或playwright爬取网页内容处理基础反爬如User-Agent。parse_content_with_llm(html: str) - dict: 调用LLM从HTML中提取标题、正文、发布时间等结构化信息。generate_report(summaries: List[dict]) - str: 调用LLM根据多篇文章摘要生成综合报告。Human Interface一个简单的Web前端展示任务状态、实时日志并提供“暂停”、“继续”、“提供输入”的按钮和输入框。5.2 协作与中断点设计我们在任务流程中预设几个关键的协作中断点搜索关键词确认Agent规划的第一步是生成搜索关键词。它可能会输出“我将使用‘大语言模型 推理优化 2024 论文’进行搜索。您是否同意或者有更具体的关键词” 此时Orchestrator会解析到这个request_human_input暂停任务将问题推给前端。目标网站筛选搜索返回了50个结果。Agent可以分析这些结果的域名然后询问“搜索结果来自arxiv.org, huggingface.co, medium.com等。您希望我优先爬取哪些域名的内容还是全部尝试” 这避免了盲目爬取可能被禁止的网站。爬取遇阻处理在爬取某个网站时fetch_webpage工具返回了403 Forbidden错误。Agent不应该无限重试或直接失败而是可以上报“尝试爬取example.com/research时遇到403错误可能触发了反爬虫机制。现有策略a) 跳过此网站b) 添加随机延迟后重试c) 切换到无头浏览器模式较慢。请选择。”报告大纲确认在生成最终报告前Agent可以先列出一个报告大纲如一、引言二、主流优化技术对比三、代表性论文解读四、未来趋势。用户可以修改这个大纲Agent再基于此填充内容。5.3 状态保存与恢复示例假设任务在“爬取遇阻处理”这个点被中断用户选择了策略c)。State Store中保存的serialized_stateJSON可能包含{ “session_id”: “task_123”, “goal”: “调研大语言模型推理优化最新进展并生成报告” “status”: “AWAITING_INPUT” “current_step”: “handle_fetch_error” “dialogue_history”: [ {“role”: “user” “content”: “帮我调研一下大语言模型推理优化的最新进展写份摘要。”}, {“role”: “assistant” “content”: “我将开始这个任务。首先我需要搜索相关的最新资料。我计划使用‘大语言模型 推理优化 2024 论文’作为初始关键词进行搜索您看可以吗”}, {“role”: “user” “content”: “可以再加上‘efficient inference’这个英文关键词。”}, {“role”: “assistant” “content”: “好的已更新关键词。正在执行搜索...”}, // ... 更多历史 {“role”: “assistant” “content”: “在爬取 ‘example.com/research’ 时遇到403错误。现有策略a) 跳过b) 延迟重试c) 用无头浏览器。请选择。”} ], “working_memory”: { “search_keywords”: [“大语言模型 推理优化 2024 论文” “efficient inference”], “search_results”: [...], “crawled_successfully”: [...], “current_failed_url”: “example.com/research” “error_detail”: “403 Forbidden” }, “pending_human_request”: { “question”: “在爬取 ‘example.com/research’ 时遇到403错误。现有策略a) 跳过b) 延迟重试c) 用无头浏览器。请选择。”, “options”: [“a” “b” “c”] } }当用户通过前端恢复此会话并输入选择“c”后Orchestrator会加载此状态。将用户的输入“c”作为一条新的user消息如“选择c用无头浏览器模式”追加到dialogue_history。清除pending_human_request。将状态置为RUNNING。将重组后的完整历史包括新消息发送给Agent Brain。Agent Brain看到最新的用户消息是“选择c”结合上下文current_failed_url它就会明白现在应该用更高级的无头浏览器工具去重试那个URL。任务得以继续。6. 避坑指南从设计到部署的实战经验在真正构建这样一个系统的过程中我遇到了不少预料之外的问题。这里分享几个关键的“坑”和应对策略。6.1 状态爆炸与存储优化随着任务进行对话历史、工具返回的原始数据尤其是爬取的HTML会非常庞大很快撑爆数据库或超出LLM上下文。对策非核心数据外链对于工具返回的巨型原始数据如完整的HTML、长的API响应不要直接存在主状态JSON里。将它们存储到对象存储如S3/MinIO或文件系统在主状态中只保存一个引用链接或文件路径。增量式摘要如前所述定期对历史进行摘要。可以每10轮对话或当历史token数达到阈值时触发一次摘要将摘要存入工作内存并清空或归档旧的历史细节。压缩与清理在保存状态前对可压缩的文本数据进行压缩如gzip。定期清理已完成或失败已久的会话数据。6.2 工具调用的副作用与幕等性很多工具调用是有副作用的发邮件、改数据库。如果中断恢复后不小心重复执行会造成严重问题。对策为操作生成唯一ID每次执行有副作用的工具时生成一个全局唯一的操作ID如UUID并将(session_id, tool_name, operation_id)作为组合键记录操作状态pending,success,failed到数据库。执行前检查在工具执行函数内部首先检查这个操作ID是否已经成功执行过。如果是则直接返回之前存储的结果而不是真正执行。设计幕等API如果可能尽量让你调用的外部API本身是幕等的比如用PUT代替POST。6.3 长时任务的超时与心跳一个复杂的Agent任务可能运行几十分钟甚至几个小时。网络可能不稳定Orchestrator服务可能重启。对策实现心跳机制Agent执行器或Orchestrator定期如每30秒更新Redis中会话的last_heartbeat时间戳。设置看门狗一个独立的守护进程定期扫描所有RUNNING状态的会话如果某个会话的last_heartbeat超过阈值如5分钟则认为该会话已僵死将其状态置为FAILED并可能触发告警或重试机制。任务可重入设计任务逻辑时尽量让每个大的步骤自身是可重试的。例如“爬取并分析A网站”这个子任务失败了恢复后可以从头重试这个子任务而不影响已经成功的“爬取并分析B网站”的结果。6.4 用户交互的超时与放弃用户可能发起一个请求后离开或者长时间不回复Agent的提问。对策设置等待超时对于AWAITING_INPUT状态设置一个超时时间如24小时。超时后可以自动将会话状态置为PAUSED并可能通过通知如邮件提醒用户。提供默认选项在设计Agent的提问时可以提供一个合理的“默认选项”。例如“如果30分钟内未收到您的回复我将默认选择方案A并继续。” 这需要谨慎评估业务逻辑确保默认选择是安全的。构建一个支持人机协作与中断恢复的AI Agent远比构建一个自动化的脚本复杂。它要求我们将软件工程的经典思想——状态管理、容错设计、用户交互——与LLM的能力深度结合。这个过程虽然充满挑战但当你看到自己打造的Agent能够像一个真正的合作伙伴一样与你并肩处理复杂任务随时可以停下讨论又能随时无缝继续时那种成就感是无与伦比的。这或许就是当下AI应用开发最具魅力的方向之一。
返回列表