资讯动态

用DeepSeek API打造Agent:从控制台到手机端的完整实操路线

发布时间:2026/9/30 6:02:10 来源:尧图企业网站定制
最近帮几个想入门Agent开发的朋友梳理了一套实操路线从终端里的控制台Agent聊起再到手机浏览器直接打开网页跟Agent对话底层全部用DeepSeek的大模型API串起来。这个项目本身不复杂但它把Agent开发的几个核心环节全走了一遍API接入、对话管理、工具调用、服务化、移动端适配。很多人在概念层面知道Agent是一个能使用工具的AI但亲手把它跑起来之后才会真正理解那个主循环到底长什么样。如果你也想把Agent从名词变成自己手机里随时能点开聊天的东西下面这一整套实践可以直接照着抄。整个项目做下来整体感觉是骨架很小但五脏俱全。控制台版本让我看清了Agent的底层逻辑手机网页版本则补上了Agent如何被更多设备使用这一课。尤其是手机网页聊天这一步很多人以为必须装App其实一个浏览器就搞定了后端起一个Web服务手机连上同一个局域网就能直接访问非常适合入门阶段的获得感。1. 项目拆解控制台Agent 手机网页聊天到底在做什么1.1 一个小Agent的完整轮廓先把这个项目的全貌说清楚。这里说的控制台Agent是一个跑在终端里的命令行程序你在键盘上敲一句指令它调用DeepSeek的大模型接口把回答再打印回终端。它是Agent最原始也最容易理解的一种形态整个交互过程没有任何花哨的东西反而能让你把注意力集中在Agent本体的逻辑上。这里说的手机网页聊天则是给同一个Agent套上一层Web外衣让它真正跑在手机浏览器里随时可以打字跟它对话。把这两个形态放在同一个项目里是因为它们的核心逻辑完全一致差别只在一层外壳。控制台版本练的是Agent内核手机网页版本练的是产品化能力——让一个原本只能在自己电脑上运行的程序变成一个别人也能用的服务。从功能上看这个项目包含四个层面第一是DeepSeek API的接入这是Agent的大脑第二是Agent主循环也就是用户输入、模型推理、结果返回的闭环第三是工具调用能力让Agent不只是聊天还能去查时间、算数学甚至按你的授权去执行一些操作第四是Web服务化把命令行程序包装成一个手机随时可访问的聊天服务。这四个层面是层层递进的每一步都在解决上一层的遗留问题。API接入解决怎么让Agent思考主循环解决怎么组织Agent的行为工具调用解决Agent怎么行动Web化解决Agent怎么被使用。把这四个点串下来整个Agent开发的骨架基本上就立住了。1.2 为什么选DeepSeek而不是本地部署有人问过我为什么不用Ollama跑一个本地模型反正部署门槛也不高。我的回答是本地部署和API调用在入门阶段解决的是两个完全不同的问题。本地部署练的是怎么把模型跑起来API调用练的是怎么基于模型做产品。我没有任何排斥本地模型的意思实际上用vllm部署DeepSeek、弄Jetson Orin这类边缘设备上的推理我也折腾过模型量化、显存规划、并发压测都是必修课。但在一个以Agent逻辑为主的项目里把时间花在Agent本身更有价值所以选了一条最短路径——直接用DeepSeek的在线API。选在线API还有几个非常实际的考虑。首先是成本DeepSeek的定价在我见过的主流大模型里属于很便宜的档位入门阶段一天可能花不了几块钱实测大部分日常对话单次请求也就是几分钱量级。其次是稳定性和并发在线API不需要自己操心手机端多人同时访问也能扛得住。最后是兼容性DeepSeek的API协议兼容OpenAI的调用格式这意味着以后想切换到其他模型代码几乎不用改生态里很多现成工具比如用Codex接入DeepSeek走的就是同一套协议。对于Agent开发新手来说这个兼容性意味着你学的东西不会绑定在某一家厂商上。提示入门阶段优先用API把Agent逻辑跑通之后如果对部署感兴趣再回头折腾本地模型学习曲线会平缓很多也更容易定位问题到底出在模型侧还是自己的代码侧。1.3 从控制台到手机端架构上的关键差异控制台版本和手机网页版本看起来只是换个入口实际架构上有一个关键差异状态管理的位置。控制台版本的状态全在进程内存里程序一结束一切归零适合自己玩。手机网页版本则必须引入会话的概念每个手机连接对应一个会话服务端保存这个会话的上下文否则手机一刷新页面Agent就会把前面聊的全忘掉。另一个差异是输入方式。控制台里你敲的是文本手机网页里除了文本输入框还要考虑触摸键盘的弹出、按钮大小、消息列表的自动滚动。这些看起来是前端的事但作为自己动手搞全栈的开发者你必须对这个差异有概念不然做成控制台版本后直接套到Web上手机上会出现各种别扭。我的建议是第一版手机页面做成极简风格一个消息列表加一个输入框加一个发送按钮就够了把交互复杂度留给后续迭代。先跑通再谈体验。2. 环境准备与DeepSeek API接入2.1 准备工作清单动手之前把装备列清楚。这个项目需要的环境非常轻语言用Python 3.10以上版本我用的是3.11。依赖就三个库requests负责HTTP请求flask负责搭Web服务python-dotenv用于管理环境变量。DeepSeek的API Key需要去它的开放平台注册获取注册后创建API Key填到项目根目录的.env文件里。一个容易被忽略的地方是网络环境。DeepSeek的API走的是标准的HTTPS接口本地开发不需要任何特殊配置直接就能调通。如果你在公司内网可能要留意一下代理设置是否会影响出网请求这个我在后面常见问题里会专门说。项目目录结构我建议这样划分agent-project/ ├── .env ├── agent.py # Agent主逻辑 ├── tools.py # 工具函数定义 ├── console_app.py # 控制台入口 ├── web_app.py # Flask服务入口 └── templates/ └── index.html # 手机端页面这样的好处是控制台版本和Web版本共享agent.py里的核心逻辑后面的所有扩展都围绕这个核心进行。2.2 三步调通DeepSeek API先说最小例子。DeepSeek的API是OpenAI兼容格式调用非常简单三步就能跑通。第一步构造请求参数包括model、messages、temperature等第二步把请求发到对话补全接口第三步解析返回结果把assistant的content打印出来。先看一个极简的Python调用示例import requests API_KEY sk-xxx url https://api.deepseek.com/chat/completions headers { Authorization: fBearer {API_KEY}, Content-Type: application/json } payload { model: deepseek-chat, messages: [ {role: system, content: 你是一个乐于助人的助手。}, {role: user, content: 你好帮我介绍一下Agent是什么。} ], temperature: 0.7 } resp requests.post(url, headersheaders, jsonpayload) data resp.json() print(data[choices][0][message][content])这个示例跑通之后你可能会有两个疑问。第一为什么model用的是deepseek-chat而不是deepseek-reasonerdeepseek-chat是DeepSeek V3对应的通用对话模型日常问答、工具调用都够用deepseek-reasoner则是带思维链推理的模型适合复杂逻辑问题但在Agent场景下我倾向于先用deepseek-chat因为reasoner的响应时间和成本都更高入门阶段没必要。第二为什么不用官方SDK官方SDK当然可以用但我故意让你先看一眼裸的HTTP请求因为只有理解了底层报文格式后面所有框架的抽象你才能一眼看穿。2.3 流式输出与上下文管理很多人在控制台Agent里遇到回答半天不动的问题原因是用了非流式的整段请求。大模型生成一个几百字的回答可能要好几秒如果不用流式用户只能干等着。流式输出的代码不复杂把stream参数设为true然后按行读取返回数据每拿到一段增量就立即打印出来。这样用户的观感是边想边出体验会好很多。上下文管理是另一个必须尽早理解的概念。大模型本身没有记忆它的记忆就是请求里的messages数组。你必须把历史对话一块块拼接再发回给API。这就带来一个很现实的问题对话越多Token消耗越大最终超过模型上下文窗口。解决办法最粗暴的是直接截断最老的对话稍微讲究一点的是做摘要压缩。控制台版本里我做的是简单的窗口滑动只保留最近十轮对话更早的内容用系统提示词里的历史摘要代替。核心逻辑可以浓缩成一段def build_messages(history, max_rounds10): messages [{role: system, content: SYSTEM_PROMPT}] for item in history[-max_rounds * 2:]: messages.append(item) return messages这段代码看似平淡但它是Agent记忆管理的地基。整体顺序是系统提示词在前然后按时间把用户消息和助手回复交替拼进去。后期想加长期记忆、向量检索本质上都是在替这个数组想办法。我见过很多项目后期改记忆功能时把代码改得很痛苦就是因为一开始没把build_messages当成一个独立模块来设计。3. 控制台Agent核心实现3.1 Agent主循环的设计我理解中的Agent并不是魔法它就是一个循环接收用户输入交给模型推理拿到模型输出再根据输出决定下一步动作。循环的终止条件有两个一个是模型认为可以直接回复用户了另一个是达到最大迭代次数防止模型陷入无限的工具调用。控制台Agent的主循环可以写成这样def run_agent(user_input, max_iter5): messages build_messages(history) messages.append({role: user, content: user_input}) for _ in range(max_iter): response call_deepseek(messages, toolsTOOLS) msg response[choices][0][message] if msg.get(tool_calls): for tool_call in msg[tool_calls]: result execute_tool(tool_call[function][name], tool_call[function][arguments]) messages.append({ role: tool, tool_call_id: tool_call[id], content: result }) else: return msg[content] return 达到了最大迭代次数我已经自动停止了。这个循环就是各种Agent框架的核心你可以把它理解为规划-行动-观察的循环模型规划要不要调工具程序执行工具然后把观察结果工具返回值喂回给模型模型再决定下一步。那些Agent工作流教程里反复强调的东西本质上就是这个循环的变体。你把这个主循环吃透了再去看市面上的Agent框架会发现它们的底层都在做同样的事情。有个细节必须提一下就是工具执行后的消息格式。DeepSeek的API和OpenAI一样要求工具执行结果以roletool的消息传回并且要把tool_call_id配对回去。这个配对关系很容易写错我第一次实现时把id写错API直接报错查了半天才发现是tool_call_id没对上。所以代码里给工具执行结果追加消息时一定要从原始的tool_call对象里取id。3.2 工具调用Function Calling实操Agent从聊天机器人变成能干活的人靠的是工具调用。所谓Function Calling本质上是在请求里额外传一份工具说明书模型看完说明书自己判断要不要调用、调用哪个、参数填什么。模型并不会真正执行你的Python函数它只是返回一个结构化的调用指令由你的程序去执行。我在项目里给Agent配了四个工具get_current_time获取当前时间calculate做数学计算read_local_file读取本地文件get_weather查询模拟天气。工具说明书大概长这样[ { type: function, function: { name: calculate, description: 执行数学计算支持加减乘除和幂运算, parameters: { type: object, properties: { expression: { type: string, description: 要计算的数学表达式 } }, required: [expression] } } } ]实操里一个很重要的体会是工具描述写得好不好直接决定模型能不能正确识别该用哪个工具。比如get_weather这个工具如果你只写天气查询模型就经常搞不清楚该传什么参数但如果你写成根据城市中文名查询该城市当前天气返回温度、风力和空气质量输入为城市中文名准确率立刻就不一样。工具说明书就是模型面前的产品手册你描述得越具体模型就越不容易出错。这算是Function Calling场景下投入产出比最高的优化点。3.3 让控制台交互更顺手的小技巧控制台Agent开发到后面真正拼的是使用体验。我分享几个自己打磨过程中的小技巧。第一个是颜色标记。在终端里用ANSI转义序列给不同角色上色用户消息用绿色Agent回复用白色工具调用日志用黄色错误用红色。一眼就能分清内容来源排查问题的时候效率翻倍。实现就几行代码def colorize(text, colorwhite): colors { green: \033[92m, yellow: \033[93m, red: \033[91m, reset: \033[0m } return f{colors.get(color)}{text}{colors[reset]}第二个是CtrlC的兜底处理。Agent在调用工具或等待模型响应的时候用户习惯性地按CtrlC想退出但默认会把整个程序直接打断。我用try-except包住主循环捕获KeyboardInterrupt后先打印当前执行状态再提示用户按Q确认退出而不是毫无交代地崩溃。这样程序显得更成熟也不会让用户一头雾水。第三个是启动时的状态回显。每次启动控制台Agent我会打印当前使用的模型、API端点、已注册的工具列表。运行一段时间之后想排查这个工具到底注册上没有一眼就能看到不用翻代码确认。这些细节虽然不起眼但对高频使用命令行工具的人来说都是刚需。4. 手机网页聊天实战4.1 从命令行到Web服务接口设计思路把控制台Agent搬到手机上的第一步是设计Web接口。很多人一上来就纠结用什么框架、要不要上异步、要不要做消息队列其实第一步思路可以更朴素你的Agent本质上就是一个函数吃进用户消息吐回回复现在要做的只是把它包成一个HTTP接口。我的设计是用Flask搭一个轻量服务暴露两个路由一个GET /返回手机页面本身一个POST /api/chat接收用户消息并返回Agent回复。会话管理用一个简单的字典session_id从请求里取没有就新建。这个设计虽然简陋但逻辑清晰没有任何晦涩的抽象非常适合Agent入门的理解阶段。谈设计时还要想清楚一个问题接口返回完整回答还是流式增量控制台版本可以流式打印但Web版如果走流式前端就要处理EventSource或fetch的ReadableStream复杂度会上升。作为首个版本我选择非流式返回让手机端先跑通闭环再考虑体验优化。后面我会单独说流式改造的思路。4.2 后端实现会话保持与响应逻辑核心后端代码其实很短。构建Chat接口时我定义一个全局的sessions字典每个key是session_idvalue是包含上下文消息的列表。收到请求后先把用户消息加到对应session再调用之前写好的run_agent把结果塞回session同时返回给前端。页面带着同一session_id刷新时就能接着上下文继续聊。from flask import Flask, request, jsonify app Flask(__name__) sessions {} app.post(/api/chat) def chat(): data request.get_json() session_id data.get(session_id, default) user_input data.get(message, ).strip() if not user_input: return jsonify({error: 消息不能为空}), 400 if session_id not in sessions: sessions[session_id] [] sessions[session_id].append({role: user, content: user_input}) reply run_agent(sessions[session_id]) sessions[session_id].append({role: assistant, content: reply}) return jsonify({reply: reply})这个sessions字典是内存态数据服务一重启就全丢了商用项目肯定要用Redis这类外部存储但入门项目里内存字典能让你把注意力放在Agent逻辑上。另一个值得后续优化的点是应该在字典里同时记录每个会话的最后活跃时间超过一小时的自动清掉避免内存无限增长。否则手机端刷新十几次、开多个标签页内存占用很快就上去了。关于流式响应我后来做了一版升级用Flask在返回前把Agent的流式输出逐步写出前端用fetch的ReadableStream逐段渲染。这里有一个关键提醒后端一旦开启流式就不能用普通jsonify直接返回必须设置传输编码为chunked并且前端需要对流式数据做缓冲解析。对入门阶段来说非流式先跑通再上流式会少踩很多坑。4.3 移动端页面与局域网访问移动端页面我用的是一个单HTML文件内嵌CSS和JavaScript没有引入任何前端框架。这不是偷懒而是想展示一个事实Agent的手机端入口完全可以从一个静态文件开始。页面上方是消息列表下方是输入框和发送按钮JavaScript用fetch向后端的/api/chat发请求收到回复后把消息追加到列表里再自动滚动到底部。手机访问的关键一步是局域网访问配置。服务启动后要监听0.0.0.0而不是127.0.0.1然后查一下电脑当前局域网IP手机连同一个Wi-Fi后在浏览器输入http://局域网IP:5000就能访问。这里必须检查电脑防火墙是否放行5000端口不然手机浏览器会直接超时。# 启动服务监听所有网卡 python web_app.py --host 0.0.0.0 --port 5000 # 查看当前局域网IPLinux/macOS ip addr show | grep 192 # 或使用 ifconfig ifconfig | grep inet 这里有一个非常常见的坑手机和电脑连的是同一个SSID但路由器开了AP隔离设备之间互访被拦截了。现象就是手机打不开网页电脑本机访问却一切正常。遇到这种情况先去路由器后台关掉AP隔离或者让手机连同一个路由器的另一个网络试一下。另外安卓手机的Chrome访问非HTTPS的局域网地址时有些版本会先弹一次安全警示点继续访问就行这是正常流程不用担心。4.4 联调过程中的几个实际坑第一个坑是中文乱码。服务端如果返回的是Flask的jsonify默认UTF-8没问题但前端如果用老的XMLHttpRequest且没设置合适的responseType某些浏览器就会把中文显示成乱码。我的解决方案是统一用fetch加JSON解析简单可靠不会再遇到编码问题。第二个坑是手机端触摸键盘遮挡输入框。页面输入框被键盘顶起来之后消息列表最后一条会被挡住用户看不到新消息。解决办法是把消息列表的滚动区域单独划出来监听visualViewport的resize事件在键盘弹起后把输入框和最新消息滚动到可视区。这是移动端适配里的经典问题遇到的频率非常高。第三个坑是PyCharm调试时控制台不输出。我在本地调试Web服务时遇到过一次Flask接口能通但控制台看不到大部分日志。后来发现是PyCharm运行配置的问题把配置里的Emulate terminal in output console选项勾上或者干脆在系统终端里运行脚本日志就正常了。这个问题和Agent逻辑本身无关但是因为它太常出现我特地在自己的排坑笔记里记了一笔。5. 常见问题速查与经验总结5.1 典型问题排查实录把实操过程中遇到的高频问题整理成一张表方便你对照排查。现象可能原因解决思路API调用返回401API Key错误或失效检查.env配置确认Key是否复制完整必要时重新生成API调用超时网络代理或防火墙拦截检查出网环境必要时临时关闭代理或配置NO_PROXY工具调用后Agent不回复tool_call_id配对错误检查roletool消息是否带正确的tool_call_id手机访问不了控制台页面服务监听的是127.0.0.1改成0.0.0.0检查防火墙入站规则手机能访问但页面白屏浏览器对局域网HTTP有拦截继续访问或换用Chrome部分安卓版本会提示不安全对话一多就报上下文超长未做上下文管理改用窗口滑动加历史摘要方案Agent执行中途异常终止达到最大迭代次数或API异常查看日志判断是工具异常还是网络异常调整max_iter或增加重试PyCharm控制台无输出运行配置问题勾选Emulate terminal in output console或直接终端运行这张表基本是我这段实践里debug经历的浓缩。比如工具调用的配对问题我第一次踩到的时候完全摸不着头脑后来一步一步拆解请求日志才发现是id没对上。再比如手机白屏当时电脑上一切正常手机却始终开不出来排查到路由器AP隔离才解决。这些问题单看都很简单但凑在一起会让一个新手非常受挫所以把它们集中列出来也算是给同样在入门路上的人一个路标。另外想提一下沙盒和安全意识。我在设计工具调用时认真考虑过Agent如果拥有执行本地命令的能力它就不再是一个无害的聊天机器人。最终我在控制台版本里把执行shell命令设计成必须人工确认的模式模型给出的命令会先打印出来等你按Y才真正执行。做工具调用功能的时候一定要想着给Agent的能力装上安全阀能力越大越需要在设计上留一道闸。5.2 做完这个项目后的几点真实体会这个项目看起来小但做完之后我自己的理解变化挺大。最初觉得Agent就是套了API的聊天机器人做完之后发现聊天只是起点工具调用让它真正能够行动而Web服务化和移动端接入让它从我的脚本变成了可被使用的产品。整个过程中最大的收获不是代码跑通了而是把Agent的几个核心概念在脑子里从抽象名词变成了具体的、可以调试的东西。有几条体会值得单独拎出来说。第一工具描述是Agent性能的隐藏杠杆。同样是调用天气工具查询天气和根据城市中文名查询当前天气返回温度、风力和空气质量带来的准确率差距是肉眼可见的。第二上下文管理不是后期优化而是从第一天就要做的架构决策。你越早把messages的组装逻辑抽象成单独模块后面加记忆、加摘要就越轻松。第三本地调试时把关键打印都留下来API请求的payload和response都存日志出问题时能第一时间还原现场。我在本地调通每一步都靠这些日志推进。写完最后这段我感觉Agent入门最好的方式就是这样一个小而完整的项目控制台版本让你看清Agent循环的内核手机网页版本让你理解产品化的路径DeepSeek API则让整个项目保持在一个非常低的成本门槛上。照着这个项目完整走一遍Agent开发的骨架就立起来了之后想往哪个方向深入路线都会清楚很多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑