资讯详情

资讯详情

手机远程操控AI Agent:FastAPI与LangGraph构建移动端控制方案

直接说结论这事儿可行而且不需要多高级的设备一台手机就能把服务端那一堆 AI Agent 全管起来。我花了一个周末把家里的工作流改成了这套“手机遥控器”模式白天在外面用手机下发任务、看进度、拿结果回家只需要处理 Agent 跑出来的异常和需要人工拍板的节点。整套方案的技术栈就是 FastAPI LangChain LangGraph再加一个轻量级任务队列移动端只做指令下发和状态展示。下面把从思路到踩坑的完整过程都写出来想复制的直接照抄想魔改的也可以顺着我的思路自己展开。1. 先搞清楚你要解决什么问题1.1 手机不是“算力中心”而是“遥控器”很多人一听“手机远程操控 AI Agent”第一反应是把大模型跑在手机上或者把 Agent 调度系统塞进手机里。说实话这方向在消费级硬件上根本走不通参数稍微大点的模型手机内存和算力直接爆表就算能跑发热和耗电也会让体验变得非常糟糕。我最后定的思路是手机只负责发指令和收结果所有 Agent 的算力、模型调用、工具执行全部留在服务端。这就跟遥控器控制空调一样——你手里拿的是面板真正制冷制热的压缩机在室外机里。这样手机端的压力几乎为零一张 Web 页面就能搞定甚至不需要装 App。这套设计解决的实际问题很明确我不可能随时随地都坐在电脑前守着 Agent 跑任务但通勤路上、午休时间、外出办事的时候手机是唯一能随手拿起来的设备。把 Agent 的控制权搬到手机上以后相当于把整套自动化体系变成了“口袋里的调度台”。1.2 方案选型从技术栈到部署方式我最终选定的核心组合是FastAPI LangGraph再配一个本地任务队列和一份简单的 JSON 消息协议。选型逻辑后面会详细说这里先讲整体方向服务端提供 HTTP API接收手机端下发的任务指令返回任务 ID 和状态。Agent 内部流程用 LangGraph 编排写清“先做什么、后做什么、做不了怎么办”。任务执行放进队列避免手机端一个请求就把服务拖垮。手机端用浏览器打开一个极简管理页本质上就是几个按钮加一张任务列表。部署方式上我用了一台云主机跑服务端再通过 HTTPS 暴露管理入口。云主机的好处是稳定、公网可达、不用折腾家里网络的动态 IP 问题。Agent 涉及的工具调用、模型 API 调用也都能在主机上顺畅跑通手机这边全程只做轻量访问。2. 架构设计可被远控的 Agent 系统长什么样2.1 我最终确定的系统架构整套系统拆成四层每层只干一件事层级职责具体实现移动端指令下发、状态轮询、结果展示极简 Web 管理页API 层接收指令、校验身份、返回任务状态FastAPI 应用队列层承接任务、控制并发、削峰填谷简单任务队列Agent 层执行多步任务、调用工具和模型LangGraph 工作流手机端发一个“帮我写一篇关于 X 的文章大纲”这种任务请求到 API 层以后API 层不会立刻跑 Agent而是先创建一个任务记录丢进队列返回一个task_id。后台 Worker 发现队列里有新任务才真正把任务交给 LangGraph 去执行。手机端拿着task_id轮询状态接口就能看到“排队中”“执行中”“执行完成”这些状态。这个“任务不阻塞请求”的设计非常关键。Agent 一个任务可能跑几分钟甚至几十分钟HTTP 请求不可能一直挂着。如果直接同步执行手机端一个请求出去就卡住了超时、断网、误操作这些不可控因素会把体验拉到最低。改成异步以后丢任务和看结果完全分离手机端体验接近“发个微信消息然后等回复”的流畅度。2.2 为什么用 FastAPI 而不是 Django / Spring不少人问过我Django 那么成熟Spring AI Agent 也很火为什么不选它们我的判断很简单——选型不是比谁功能多而是比谁更贴合场景。Django 确实生态丰富自带 Admin 后台、ORM、认证体系但它的异步支持相对晚默认的同步模型在高并发 I/O 场景下有点吃劲。我的场景是大量短请求手机端频繁轮询状态 少量长任务Agent 实际执行FastAPI 原生异步接口处理这种 I/O 密集型的杂活非常顺手性能表现也稳。Spring 系适合 Java 技术栈成熟的团队如果你本身是 Java 生态用 Spring AI Agent 也没问题但我的 Agent 编排、模型调用、工具封装全在 Python 生态里硬切 Java 等于把已经熟练的路推倒重走。Rust 写的 Agent 性能确实强但团队学习成本高、迭代速度慢不是一个人玩转自动化工作流的友好选择。FastAPI 还有一个隐性优势自动生成 OpenAPI 文档。手机端联调的时候直接打开/docs页面就能看到所有接口的定义和参数省了一堆来回沟通的时间。2.3 用 LangGraph 编排 Agent而不是自己堆 if-else早期写 Agent 的时候我习惯在代码里写一大串if判断来决定下一步调什么工具这种写法在流程固定时没问题一旦出现分支多、需要人工介入、有循环重试的情况代码马上变成一团乱麻。LangGraph 解决的核心问题是把流程变成一张可执行的图。每个节点是一个动作比如“搜索信息”“调用模型生成文案”“整理结果”边定义了动作之间的流转条件。这样有两个直接好处流程可见。跑完一个任务我能看到它走了哪些节点卡在哪个环节改了哪条边影响多大。流程可控。可以给节点加回调、超时、重试甚至接消息通知把异常干预做到每一个环节。而且 LangGraph 天然适配 LangChain 生态模型调用、工具封装都是现成的省去自己造轮子的过程。一个手机远控场景最怕的就是 Agent 内部变成一个黑盒——手机端只看到“执行中”但不知道里面在干嘛。LangGraph 的节点状态机制让我可以把“当前在哪个节点、上一步结果是什么”透传出去手机上能看到任务推进的过程比自己写一堆日志再解析靠谱得多。3. 手机端怎么“反控”服务端的 Agent3.1 消息协议设计让手机和 Agent 说同一种语言手机端和服务端之间的通信我做了一份特别简单的 JSON 协议核心就三个接口创建任务POST /tasks请求体里包含任务类型和参数。查询状态GET /tasks/{task_id}返回状态、进度、结果。查看可用 AgentGET /agents展示当前服务端注册了哪些能干活的工作流。任务请求体的格式参考{ agent_id: content_writer, action: generate_outline, params: { topic: 如何用手机管理AI Agent, style: 专业口语化, depth: 3 }, callback: optional_webhook_url }agent_id用来告诉服务端调哪个 Agent 工作流action是具体动作params是参数callback是可选的回调地址。协议设计得越简单越好因为手机端没有那么多复杂逻辑能发 JSON 和解析 JSON 就够了。这里有一条经验协议字段宁少勿多。我一开始设计了十几个字段后来发现真正用到的就五六个多余的字段不仅让文档难写手机端参数校验也麻烦。简约协议配合清晰的字段命名长期维护成本最低。3.2 状态接口让手机随时知道 Agent 在忙什么任务状态我用一个简单的枚举来描述状态含义手机端展示queued已进队列等待执行排队中runningAgent 正在执行执行中need_review需要人工确认节点待处理completed执行完成已完成failed执行失败失败手机端每隔两三秒轮询一次状态接口拿到对应状态做展示。轮询看起来笨但对这种低频控制场景来说最省事不需要 WebSocket也不需要维护长连接一台手机同时盯几个 Agent 绰绰有余。当任务跑出结果服务端会返回一串结构化的数据手机端直接渲染成卡片。比如让 Agent 写文章大纲返回的可能是标题、分节、每节要点让 Agent 分析数据返回的可能是结论和图表数据的 JSON。手机端不需要理解内容本身只需要按预设的模板展示。3.3 身份验证与安全不是任何设备都能发指令手机远程下发指令意味着服务端暴露在公网上必须做身份验证否则任何人拿到地址都能操控你的 Agent。我用的方案很朴素但很有效在 FastAPI 里加了一个简单的Authorization: Bearer token校验。一份长期 API Token 给手机端管理后台用Token 本身存手机浏览器里不写死在页面代码中。所有的 Agent 执行行为都限定在提供方配置的权限范围内不能让遥控者通过 Agent 越权操作系统。安全配置的底线意识还是要有的宁可牺牲一点便利性也不能把管理入口裸奔在公网上。4. 核心代码与实操过程4.1 搭建 FastAPI 服务骨架我的 FastAPI 服务端代码非常规整核心就是一个创建任务的接口和一个查询状态的接口。直接贴一份简化后的代码隐藏掉业务细节框架结构大家可以参考# main.py from fastapi import FastAPI, HTTPException, Header from pydantic import BaseModel from typing import Optional, Any import uuid import asyncio app FastAPI(titleMobile Agent Controller) # 任务请求体 class TaskRequest(BaseModel): agent_id: str action: str params: dict {} callback: Optional[str] None # 用一个字典模拟任务存储 TASK_STORE {} VALID_TOKENS {your_long_random_token_here} def check_auth(authorization: str): token authorization.replace(Bearer , ).strip() if authorization else if token not in VALID_TOKENS: raise HTTPException(status_code401, detailInvalid token) app.post(/tasks) async def create_task(task: TaskRequest, authorization: str Header(default)): check_auth(authorization) task_id str(uuid.uuid4()) TASK_STORE[task_id] { task_id: task_id, status: queued, agent_id: task.agent_id, action: task.action, params: task.params, result: None, progress: [], } # 丢入后台任务队列此处用 asyncio.create_task 演示异步执行 asyncio.create_task(run_agent(task_id, task)) return {task_id: task_id, status: queued} app.get(/tasks/{task_id}) async def get_task(task_id: str, authorization: str Header(default)): check_auth(authorization) if task_id not in TASK_STORE: raise HTTPException(status_code404, detailTask not found) return TASK_STORE[task_id] async def run_agent(task_id: str, task: TaskRequest): # 模拟 Agent 执行可以替换为 LangGraph 工作流调用 TASK_STORE[task_id][status] running TASK_STORE[task_id][progress].append(start running) try: await asyncio.sleep(3) # 模拟耗时操作 TASK_STORE[task_id][result] { message: fFinished {task.action} with params {task.params} } TASK_STORE[task_id][status] completed except Exception as e: TASK_STORE[task_id][status] failed TASK_STORE[task_id][result] {error: str(e)}这段代码里asyncio.create_task只是最简单的演示。生产环境里我建议把任务丢给真正的队列比如 Celery 或者简单的 Redis 队列因为asyncio.create_task在进程重启后会丢任务队列持久化才经得起折腾。4.2 把 LangGraph 工作流接进任务执行FastAPI 的接口负责接单真正干活的还是 LangGraph 工作流。我定义了一个非常典型的内容生成 Agent包含“接收主题 - 搜索资料 - 生成大纲 - 检查并返回”四个节点。# agent_workflow.py from langgraph.graph import StateGraph, END from typing import TypedDict, Annotated, List import operator class AgentState(TypedDict): topic: str search_results: List[str] outline: str step_count: Annotated[int, operator.add] def search_node(state: AgentState): # 模拟搜索资料可以替换为真实的搜索 API 调用 state[search_results] [fresult about {state[topic]}] state[step_count] 1 return state def generate_outline_node(state: AgentState): # 调用大模型生成大纲这里用字符串模拟 state[outline] f这是关于{state[topic]}的文章大纲 state[step_count] 1 return state def review_node(state: AgentState): # 校验并返回 if 大纲 in state[outline]: state[step_count] 1 return state else: return state graph StateGraph(AgentState) graph.add_node(search, search_node) graph.add_node(generate_outline, generate_outline_node) graph.add_node(review, review_node) graph.set_entry_point(search) graph.add_edge(search, generate_outline) graph.add_edge(generate_outline, review) graph.add_edge(review, END) app_workflow graph.compile()这个工作流跑起来以后我可以在手机端看到每一步的进度开始搜索、生成大纲、校验返回。如果想让手机端实时看到当前节点只需在 FastAPI 的任务执行循环里不断更新TASK_STORE[task_id][progress]把节点名称追加进去。这样做的好处是手机端不用猜 Agent 在干什么一眼就能看到进度。4.3 并发和队列好几个 Agent 同时跑也不慌手机遥控的第一反应通常是“一口气发好几个任务”谁都不愿意一个个排队等。但 Agent 任务往往要调模型 API、要请求外部服务同时开太多并发不仅会拖垮服务端还可能把第三方 API 的配额打爆。我给系统加了一个简单的并发控制层核心是一个信号量限制同时执行的任务数量# task_runner.py import asyncio MAX_CONCURRENT 3 semaphore asyncio.Semaphore(MAX_CONCURRENT) async def run_agent_with_limit(task_id, task): async with semaphore: # 实际运行 LangGraph 工作流 app_workflow.invoke(task.params) update_task_store(task_id, completed)设置并发上限以后手机端发 10 个任务系统会自动把超过上限的任务排在队列里。一开始我不知道自己需要多少并发实测下来 3 个左右最稳模型 API 的响应速度和服务端资源都能扛住。等后面真的需要更多并发再调大信号量和增加 Worker 数量即可。手机端轮询也要考虑频率问题。我默认设置为 2 秒一次一个页面同时显示 5 个任务时请求量是 2.5 QPS对 FastAPI 来说毫无压力。为了防止用户手滑疯狂点刷新我在前端做了节流同一页面最短刷新间隔设为 2 秒。4.4 远程访问配置手机在外面也能连上服务服务端代码搞定以后剩下的问题是怎么让手机在外面通过公网访问。我直接在云主机上做三层保障把 FastAPI 服务绑定到本机的127.0.0.1:8080不直接暴露公网端口。用 Nginx 做反代对外监听 443 端口配置 HTTPS 证书。云主机安全组只放行 443 端口封掉 22 以外的所有端口。HTTPS 是必须的因为手机在外面连的是公共 Wi-Fi如果走明文 HTTP登录的 Token 很容易被抓包拿到。配置完 HTTPS 和反代以后手机浏览器直接访问https://your-domain.com就能打开管理页面。管理页面我也做得特别简朴就是 HTML JavaScript调几个接口展示任务列表、状态和结果。没有用重前端框架因为这种控制页面的核心就是“直来直去”不需要复杂交互。为了手机上用得舒服我把按钮尺寸调大了一些页面宽度限制在移动端友好的范围内。5. 踩坑实录这些问题我折腾了很久5.1 任务卡死Agent 掉进循环里出不来第一次实测的时候我给 Agent 配了“搜索资料”的工具让它反复搜索并总结。结果手机端一直显示“执行中”任务跑了 20 多分钟都没结束。排查以后发现是 Agent 内部陷入了一个“搜索 - 结果不满足 - 再搜索”的死循环。解决方案是给 LangGraph 工作流加了最大步数限制和节点超时控制。LangGraph 支持在编译时设置递归深度或者给关键节点加timeout超过时间就自动走失败分支。这个坑给我的启发是手机远控场景下Agent 的“失控时间”会被拉长。电脑前发现卡死还能马上手动终止手机上发现异常时往往人不在电脑旁所以必须靠服务端机制兜底。现在我的每个 Agent 都有硬性步数上限跑超了就自动终止并返回失败原因。5.2 手机端掉线导致结果丢失用的人都知道手机锁屏、切换 App、网络波动都是常态。最开始我的手机端是直接等待同步结果也就是发完任务后一直保持页面打开。一旦锁屏切走回来发现结果已经出来了但页面刷新后状态不同步。后来我在服务端做了任务结果持久化即使手机端断线重连后也能通过task_id拉取到完整结果。持久化用的就是 SQLite存任务 ID、状态、结果 JSON查询快、单机零依赖没必要为了这个场景上重型数据库。5.3 并发扎堆手机发了一堆任务模型 API 被限流有一次我把 8 个任务一次性推给系统信号量上限是 3结果队列里堆满任务一部份还触发了模型 API 的限流返回 429。排查后发现不是队列的问题而是我在 Agent 里调用模型 API 时没做重试和退避。后来给所有外部 API 调用加了一层统一的重试逻辑遇到 429 或 5xx 错误等待 1 秒、2 秒、4 秒指数退避重试最多重试 3 次。加上信号量限流以后基本没有再出现过 API 被限流的情况。5.4 端口暴露的安全隐患一开始为了图省事我让 FastAPI 直接监听公网端口结果一晚上收到几百次扫描请求。虽然 Agent 本身有权限控制但这种裸奔式暴露本身就是让攻击者免费试错。改掉以后我确立了“公网只留 HTTPS 443”的原则。服务端代码变化不多但安全等级完全不一样。6. 常见问题速查表问题现象根本原因解决方案手机刷新后任务状态消失任务没做持久化用 SQLite/Redis 记录任务状态页面一直显示“执行中”Agent 死循环或节点无超时设置最大步数、节点超时任务全挤在一起卡顿并发上限未设置用信号量/队列限制并发数移动网络下拉取结果超时请求等待时间过长改异步任务先返回 task_id 再轮询手机误操作重复发任务缺少幂等控制前端提交置灰、服务端查重管理页面被陌生人访问端口暴露且无鉴权反代 HTTPS Token 校验自己做这套系统最大的体会是手机远程操控 AI Agent 的难点不在于怎么在手机上写代码而在于把服务端架构设计得足够“耐折腾”。手机只是入口真正决定体验的是服务端的稳定性、安全性和任务可靠性。把这三件事做好手机遥控所有 Agent 就是水到渠成的事。最后再分享一个小技巧我在手机主屏幕给管理页面建了个快捷方式打开就像原生 App 一样完全无浏览器冗余元素。配合桌面小组件显示任务列表随时扫一眼就知道 Agent 们的状态体验比坐在电脑前守着还舒服。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →