☰
DeepSeek实战:300行代码从零搭建命令行与手机端Agent
2026/9/30 6:01:59 网站建设 项目流程

2025年要是你还只在热搜上看Agent相关话题,却一直没真正上手搞一个,那这篇东西就是写给你的。我最近自己从零做了一套基于DeepSeek的Agent开发实践,一头是跑在控制台里的命令行Agent,另一头是手机网页聊天入口,两边共用同一套后端逻辑、同一个模型接口,所有代码加起来不到三百行。整个过程没有用任何重量级框架,没有图形界面,就是命令行里敲代码、浏览器里点页面,把一个"能听懂人话、能连续对话、能被手机远程调用的智能体"实实在在搭了出来。

这个项目解决的最大痛点,说白了就是"不知道怎么迈第一步"。很多人看过吴恩达的Agent教程,也知道有LangChain、AutoGen这类框架,但是真到自己动手的时候,会被框架概念、抽象封装绕晕。我的做法是把所有抽象剥掉,直接对着DeepSeek的API写对话循环,把Agent最核心的逻辑跑通,再加上一个手机上能用的网页聊天壳子。这套东西适合三类人:刚入门的开发者想搞懂Agent内部运行逻辑的,想快速验证DeepSeek API能做点什么的产品同学,以及被各种Agent框架折腾到怀疑人生的实践派。下面我把整个开发过程、踩坑记录、参数怎么调、手机端怎么适配,全部摊开讲。

1. 项目概述:用DeepSeek从零做一个Agent需要哪些准备

1.1 这个项目最终做出了什么效果

先把成果说清楚。整个项目拆成两个入口:第一个入口是控制台(也就是命令行终端),你敲一行字发过去,Agent在终端里流式地回复内容,支持连续多轮对话,而且对话历史会被保留,上下文是连贯的;第二个入口是手机网页聊天,同一套后端逻辑,换成了适合手机触摸操作的聊天界面,手机和电脑处在同一个局域网时,通过浏览器访问电脑的IP地址就可以聊天,不需要装App,不需要上架应用商店。

很多人会误解"控制台Agent"里的控制台,有人以为是云厂商的管理控制台,有人以为是浏览器开发者工具里的Console面板。其实在Agent开发的语境下,控制台指的就是命令行终端(Terminal),打开之后是一个光标闪烁的黑色窗口,你打字、回车,程序输出结果。这是最原始的计算机交互方式,但恰恰是理解Agent底层逻辑最好的载体——因为没有任何UI帮你遮遮掩掩,一次调用、一次返回、上下文怎么拼接,全都摆在你面前。

手机网页聊天则是把同一条对话链搬到了浏览器里。我做了个极简的聊天页面,没有昵称系统、没有头像、没有数据库,只有消息列表加输入框。手机端软键盘弹出时输入框自动顶上去,消息多了自动滚到底部,整个过程核心就是调后端接口。这两个入口加起来,你基本能体会到Agent在"固定工作场景"和"移动随手用"两种形态下的区别。

1.2 为什么用DeepSeek做入门首选

选择DeepSeek不是因为它热度高,而是它几乎是为"入门Agent开发"量身定做的。首先,它的API接口完全兼容OpenAI的格式,这意味着网上大量以OpenAI为示例的教程代码,你只需要改一个base_url和api_key就能跑起来,学习成本和迁移成本非常低。其次,DeepSeek的中文理解能力在同类模型里是明显偏强的,我用它做中文对话测试,语义理解、上下文关联性、指令遵循程度都很稳定,很少有答非所问的情况。

价格也是一个非常现实的因素。Agent开发需要反复调试,每一次调试都是一次真实调用,如果模型定价高,调试时手会抖。DeepSeek的定价属于"随便造不心疼"的档位,我整个项目开发加测试调用了上万次,费用完全在可接受范围内。另外DeepSeek也支持工具调用(Function Calling)能力,这在Agent后续扩展"让模型调用外部工具"时非常关键,入门阶段先把对话跑通,后面要接搜索、接计算器、接数据库,接口都给你留好了。

我把对比列成一张表,方便你快速做取舍:

对比项DeepSeek其他主流闭源模型本地小模型
API兼容性兼容OpenAI格式,改动小各家有各自格式需自己封装接口
中文理解很强中上参数量小,容易跑偏
价格极低,适合反复调试相对偏高只需电费,但需硬件
工具调用支持Function Calling普遍支持部分支持
上手门槛极低,几分钟跑通中需要部署环境

如果你的电脑配置不错,本地部署DeepSeek模型也可以考虑,但那是另一个复杂度量级的事——要处理显存、量化、推理框架一系列问题。入门阶段,直接用官方API是最合理的路径。

2. 拆解Agent的核心组成:不是玄学,是四件套

2.1 大脑:大模型负责理解和决策

Agent这个词看起来很高级,但把外壳剥掉,核心就是一个大模型加三个辅助模块。我习惯这样类比:大模型是Agent的大脑,负责理解你说的话、判断接下来怎么办、组织语言回复你。你在控制台打下"帮我写一段Python代码算斐波那契数列",大模型读到这句话后,在内部进行推理,然后输出一段答案文本。这个过程的本质就是一个API调用,和你在网页上问ChatGPT没有任何区别。

但这个"大脑"有一个关键特性:它是无状态的。什么意思?大模型本身不记得你上一次问了什么。它就像一个只活在当下的天才,你每次提问,它都是"第一次"见到你。而没有状态的Agent不可能进行连续对话,所以必须引入第二个模块——记忆。

真正让Agent区别于普通聊天机器人的,是它把"理解"和"行动"串起来了。大模型不仅负责回复文字,还能根据你的指令决定调用哪些工具、以什么顺序调用。入门阶段你可以不马上实现工具调用,但心里必须清楚,这个能力边界是存在的,后面扩展全靠它。

2.2 记忆:上下文管理的底层逻辑

记忆模块在实现层面就是一组消息数组,里面按顺序存放每次对话的"用户发言"和"模型回复"。每次请求时,把这个数组连同最新的用户发言一起发给模型接口,模型就拥有了完整的对话背景。

我用一个生活化的例子解释:你在手机上和朋友微信聊天,朋友不可能记住你们从加好友第一天起的所有消息。他只是在看当前聊天记录时,能从上面滑动回顾最近几天的内容。Agent的上下文管理也是如此——它把之前聊过的内容"贴在"新问题前面,让模型在理解新问题时能参考旧信息。

但这里有个工程问题:上下文是有限度的。DeepSeek的上下文窗口虽然很大,但如果你无限地往消息数组里塞内容,总有一天会突破窗口上限。所以实际开发中需要做截断策略:保留最近N轮对话,超出部分丢弃,或者用摘要的方式把早期对话压缩成一句总结再放回数组。入门阶段最简单粗暴的策略就是"只保留最近20轮",够用且不容易触发长度限制。

2.3 手脚:工具调用让Agent真正"能做事"

纯对话的Agent其实谈不上"做事",它只是"说话"。真正让它从聊天机器人进化为Agent的关键,是工具调用能力。通俗地讲,就是你让Agent执行某个动作时,模型不会自己去操作系统,而是返回一个结构化的"工具调用意图",由你的代码真正去执行那个操作,再把结果返回给模型,模型综合结果给出最终回复。

举个例子:你对Agent说"帮我查一下现在北京天气"。模型识别出这个请求需要工具支持,于是返回一个调用指令,类似"call(get_weather, city=北京)"。你的程序收到这个指令后,去天气API拉取数据,把结果拼成一段文字塞回对话里。模型看到天气数据后,再组织成一段自然语言回复你。这个过程就是Agent的"手脚",工具是你的程序写的,模型只负责决定"什么时候用哪只手、怎么用"。

入门阶段可以先不实现工具调用,但建议架构上留一个口子。具体做法是在处理模型回复时,先判断返回内容里是否包含工具调用标记,如果有就走工具执行分支,没有就直接输出文本。后面加工具,只需要按约定的格式注册一个新函数,不用改动主流程。

2.4 控制台与网页:两种交互形态的取舍

同一个Agent,跑在控制台和跑在手机网页里,体验差异非常大。控制台的优势是零UI成本,你只需要一个readline读取输入、一个循环处理对话,逻辑最透明;劣势是它只能坐在电脑前用,没有延续性。手机网页的优势是随时可用,打开了就是一个小助手,劣势是需要处理移动端的适配细节,比如软键盘、触摸滚动、断线重连。

我建议先做控制台版本,跑通了再套网页壳子。原因很简单:控制台版本可以帮你把所有注意力集中在"Agent本身的逻辑"上,不被UI问题分心。网页版遇到的大多数问题其实是前端问题,和Agent关系不大。两者共用的核心模块是消息管理和API调用逻辑,这一块设计好了,换任何交互界面都很轻松。

3. 控制台Agent完整实现:从命令行跑通第一句对话

3.1 环境准备与API密钥配置

先说环境。我用的是Python 3.10以上的版本,OpenAI官方Python SDK,装一下就行:

pip install openai

SDK本身不区分厂商,DeepSeek因为兼容OpenAI格式,直接用同一个SDK,只需要把base_url指到DeepSeek的接口地址,把api_key换成你自己的。API密钥在DeepSeek开放平台的账户后台生成,生成之后我用环境变量来管理,避免把密钥硬编码在代码里:

export DEEPSEEK_API_KEY="你的密钥"

为什么用环境变量而不是直接写在代码文件里?两个原因。第一,代码可能会分享给别人,密钥泄露了会被盗刷;第二,换模型厂商时,只需要换环境变量,不需要改代码。我在项目里把密钥读取封装成了一个小函数,程序启动时如果发现环境变量不存在,直接提示用户配置再退出,比代码里写死显得专业很多。

模型的参数选择上,我用的是deepseek-chat,这是DeepSeek的主力对话模型,响应速度快,价格也低,适合做高频对话场景。如果你需要更强的推理能力,可以换deepseek-reasoner,它擅长复杂逻辑分析和数学题,但响应时间会长一些。入门阶段用deepseek-chat就够了。

3.2 对话循环与流式输出代码实现

控制台Agent的核心是一个无限循环:等待输入,拼上下文,发请求,打印回复。我直接给出完整代码并逐段解释:

import os from openai import OpenAI client = OpenAI( api_key=os.getenv("DEEPSEEK_API_KEY"), base_url="https://api.deepseek.com" ) SYSTEM_PROMPT = "你是一个简洁实用的编程助手,回答问题时直接给出解决方案。" history = [{"role": "system", "content": SYSTEM_PROMPT}] print("控制台 Agent 已启动,输入 exit 退出,输入 clear 清空上下文") while True: user_input = input("\n你 > ") if user_input.lower() == "exit": break if user_input.lower() == "clear": history = [{"role": "system", "content": SYSTEM_PROMPT}] print("上下文已清空") continue history.append({"role": "user", "content": user_input}) response = client.chat.completions.create( model="deepseek-chat", messages=history, stream=True, temperature=0.7 ) print("Agent > ", end="") reply = "" for chunk in response: delta = chunk.choices[0].delta.content if delta: print(delta, end="", flush=True) reply += delta history.append({"role": "assistant", "content": reply})

这段代码看起来短,但已经把Agent的"记忆"和"流式输出"两个核心点都覆盖了。history数组就是记忆模块,每次请求都完整携带;stream=True开启流式输出,模型生成一个字就送回来一个字,终端里能看到打字机效果,体验比干等十几秒出完整结果好得多。

流式输出有个细节值得注意:必须用flush=True强制刷新输出缓冲区,否则控制台会攒满一批才打印,打字机效果就没了。另外,收到的每个delta片段,我一边打印一边拼进reply,循环结束后把完整的回复追加进history,确保上下文里存的是完整文本而不是零散片段。

temperature参数控制回答的随机性。0.7是我试下来比较平衡的值,既不会太死板也不会胡言乱语。如果做代码生成类的任务,我建议调到0.2以下,代码需要确定性;如果是闲聊或创意写作,0.8以上会更活泼。这个参数多调几次就有感觉了。

还有一个容易忽略的细节:我在初始化时加了一条system角色的消息。系统提示词的作用是给Agent设定一个"人设"和"行为准则"。比如我设的是"直接给出解决方案",模型就不会回复一堆"这是一个很好的问题,让我来帮你分析"之类的废话。你完全可以根据自己的场景改写这条提示词,这是控制Agent行为风格最便宜有效的手段。

3.3 上下文管理与多轮对话优化

在上面代码里,history数组会无限增长,每轮对话都追加两条消息。跑得时间长了,迟早会超过模型上下文长度的上限。我用的优化方案是滑动窗口:只保留最近的20轮对话,超过的部分直接丢弃。

实现非常直观:

MAX_HISTORY_ROUNDS = 20 def trim_history(history, max_rounds): system_msg = history[0] chat_msgs = history[1:] if len(chat_msgs) > max_rounds * 2: chat_msgs = chat_msgs[-(max_rounds * 2):] return [system_msg] + chat_msgs

每轮对话包含用户和助手两条消息,所以20轮就是40条消息。调用API前先跑一遍这个函数,确保发送的payload不会超限。我在实际使用中还会加一个"字符数兜底",如果某条回答特别长(比如生成一大段代码),即使轮数没超,消息体量也可能很大。所以保险做法是同时限制轮数和总字符数,谁先超标就砍谁。

上下文管理还有个进阶技巧:早期对话摘要化。如果你希望Agent记住很久之前的决定,但又不占上下文空间,可以定期把早期对话用模型本身做一次总结,生成一段摘要文本作为一条system消息放在最前面。这个技巧先不展开,但它方向是对的——你在上下文管理上的投入,直接决定Agent在长会话中的表现。

4. 手机网页聊天实现:让Agent跟着你走

4.1 为什么用网页而不是App

手机网页聊天听起来好像很复杂,其实是我在这项目里最省事的部分。我直接放弃了做App的想法,原因有三点:第一,上架App需要签名、审核、商店账号,这些事对一个验证原型的项目来说成本不可接受;第二,跨平台问题,iOS和Android两套都要适配;第三,聊天场景的核心是"打开就用",把网址存到手机桌面,体验和App差距很小。

网页方案的技术路线是这样的:后端继续用Python写一个简单的Web服务,暴露一个/chat接口接收对话消息并返回Agent回复;前端是一个独立HTML页面,做成移动端优先的聊天界面,通过fetch请求调后端接口。手机浏览器和电脑在同一个Wi-Fi下,访问http://电脑IP:5000就可以了。这就是一个完整的"手机网页聊天"形态。

比起App,网页方案唯一的短板是消息推送——你没有系统级的通知能力,用户不在页面里就收不到消息提醒。但对这个阶段的项目,聊天是主动发起的行为,用户打开页面就是要聊的,不需要推送。

4.2 后端接口与前端页面的核心代码

后端我用Flask,实现一个POST接口,接收前端传来的消息数组,直接转发给DeepSeek的API,返回模型的回复:

from flask import Flask, request, jsonify from flask_cors import CORS from openai import OpenAI import os app = Flask(__name__) CORS(app) client = OpenAI( api_key=os.getenv("DEEPSEEK_API_KEY"), base_url="https://api.deepseek.com" ) SYSTEM_PROMPT = "你是一个随身的AI助理,回答简洁、准确。" @app.route("/chat", methods=["POST"]) def chat(): data = request.get_json() messages = data.get("messages", []) if not messages or messages[0].get("role") != "system": messages.insert(0, {"role": "system", "content": SYSTEM_PROMPT}) response = client.chat.completions.create( model="deepseek-chat", messages=messages, temperature=0.7 ) reply = response.choices[0].message.content return jsonify({"reply": reply}) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)

这里有个细节:兼容性。前端传过来的messages数组直接就是OpenAI接口需要的格式,所以我不用在后端重新组织结构,只检查有没有system消息、没有就补一条。host="0.0.0.0"是必须的,否则Flask默认只监听本机回环地址,手机通过局域网IP根本访问不到。

前端页面我写了一个单文件HTML,核心是消息列表区、输入框、发送按钮,加少许JavaScript处理发送和渲染。关键代码是这样:

<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"> <style> body { margin: 0; font-family: sans-serif; background: #f5f5f5; } #chat { height: calc(100vh - 60px); overflow-y: auto; padding: 12px; box-sizing: border-box; } .msg { margin: 8px 0; padding: 10px 12px; border-radius: 12px; max-width: 80%; line-height: 1.5; white-space: pre-wrap; word-break: break-word; } .user { background: #007aff; color: #fff; margin-left: auto; } .agent { background: #fff; color: #222; } #bar { display: flex; gap: 8px; padding: 8px; background: #fff; border-top: 1px solid #ddd; } #input { flex: 1; font-size: 16px; padding: 10px; border-radius: 8px; border: 1px solid #ddd; } #send { width: 64px; border: none; background: #007aff; color: #fff; border-radius: 8px; font-size: 16px; } </style> </head> <body> <div id="chat"></div> <div id="bar"> <input id="input" type="text" placeholder="输入消息..."> <button id="send">发送</button> </div> <script> const chat = document.getElementById('chat'); const input = document.getElementById('input'); let messages = []; function addMsg(role, content) { const div = document.createElement('div'); div.className = 'msg ' + role; div.textContent = content; chat.appendChild(div); chat.scrollTop = chat.scrollHeight; } async function send() { const text = input.value.trim(); if (!text) return; input.value = ''; addMsg('user', text); messages.push({ role: 'user', content: text }); const resp = await fetch('/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages }) }); const data = await resp.json(); const reply = data.reply; addMsg('agent', reply); messages.push({ role: 'assistant', content: reply }); } document.getElementById('send').onclick = send; input.addEventListener('keydown', e => { if (e.key === 'Enter') send(); }); </script> </body> </html>

在手机端两个细节很关键。第一,meta viewport必须设置,否则手机浏览器会按980像素宽度渲染桌面页面,字小得没法看;第二,输入框的font-size要设为16px以上,否则iOS Safari在聚焦输入框时会自动放大页面,造成缩放抖动。这两个问题我都是真机测试时才发现的,开发时用电脑浏览器模拟器根本看不出问题。

4.3 流式输出与长连接的处理方案

上面的代码用了"请求-响应"模式,点击发送按钮后要等模型完全生成完,前端才拿到完整回复。网络好时还好,网络差时转圈好几秒,体验很差。更优的做法是流式响应:后端通过stream=True接收模型增量,前端用fetch配合ReadableStream逐字读取并渲染。

流式方案的改动也不复杂。后端先返回一个text/event-stream格式的响应,前端逐块读取。我后来迭代时加了流式,效果明显提升,手机上的打字机效果很带感。不过流式要注意断开重连的问题——如果用户中途切换页面,连接断掉,后端只产出了一半回复,这时要设计一个reconnect机制或者至少在页面上给出"回复中断,请重试"的提示。我初期实现时忽略了这个问题,结果切到微信看了一眼再切回来,回复就卡死了。

对于入门项目,我建议先做非流式,跑通了再升级流式。流式牵扯到前端状态管理、异常处理,不是三五行代码能搞定的。先让核心对话链完整,再优化体验,顺序不能反。

5. 实操中遇到的典型问题与排查思路

5.1 高频问题速查表

问题现象可能原因解决方式
API调用返回401错误DEEPSEEK_API_KEY环境变量没设置或密钥错误检查环境变量名,重新复制密钥
控制台打印中文乱码Windows终端默认编码不是UTF-8运行chcp 65001切换代码页,或设置Python环境变量PYTHONIOENCODING=utf-8
流式输出没有打字机效果缺少flush=True在print函数中加flush=True
手机访问http://IP:5000连不上防火墙拦截,或Flask未监听0.0.0.0确认host="0.0.0.0",放行5000端口或关闭防火墙测试
对话超过一定轮数后报错上下文超出模型窗口限制实现滑动窗口截断,保留最近20轮
手机页面文字很小缺少viewport meta标签添加<meta name="viewport" content="width=device-width, initial-scale=1.0">
手机输入框聚焦时页面放大输入框字号小于16px触发iOS自动缩放输入框font-size设为16px以上
后端收到请求但回复很慢网络问题或模型本身推理时间长查看DeepSeek状态页,或改用deepseek-chat而非deepseek-reasoner
前端fetch请求跨域报错Flask未开启CORS使用flask-cors扩展,加上CORS(app)即可

这张表里的问题,除了最后两个,我在开发过程中都实际遇到过。其中编码问题是最隐蔽的——Windows PowerShell默认代码页是GBK,Python打印UTF-8中文时,界面直接显示乱码。查了很久才发现不是代码问题,而是终端编码问题。

5.2 最值得记住的实操教训

第一,API密钥管理真的不是小事。项目刚开始我把密钥直接写在.py文件里,后来把代码片段发给朋友看,顺手就把密钥发出去了一半,吓得赶紧去后台重置。从那以后我定了规矩:任何代码上传前必须检查有没有api_key字样的硬编码,密钥统一走环境变量。这条纪律救了很多人,也包括后来的我。

第二,上下文管理不是"技术优化",而是"功能保障"。刚开始我完全不截断上下文,跑了五十多轮对话后请求直接报错,仔细一看发现一个请求里塞了几万token。入门阶段你可能觉得20轮够用,但真用起来你会发现,20轮看似很多,一旦涉及长文本讨论,很快就会被撑爆。我后来的策略是:轮数和字符数双重限制,同时把偶尔需要"长期记忆"的内容想办法提前固化到系统提示词里。

第三,手机端调试要在真机上进行。浏览器的设备模拟器能模拟尺寸,但模拟不了触摸键盘、模拟不了页面缩放、模拟不了真实的网络延迟。我的经验是,电脑上用模拟器调样式,手机真机上调交互,缺一不可。

第四,网络超时不要无限等待。DeepSeek API偶尔会出现响应慢的情况,如果前端没有超时控制,用户等十几秒看不到任何反馈,会误以为程序卡死了。我最后加了一个简单的前端超时提示:超过15秒没有完整回复,显示"响应较慢,正在等待模型继续生成"。这种小细节,才是决定一个工具"能不能拿来常用"的关键。

6. 把Agent接着往深做的几条路

控制台Agent和手机网页聊天跑通之后,这个项目已经是一个"最小可用"的Agent骨架了。接下来你有几个明确的扩展方向,我在项目开发过程中也陆续验证了一部分。

第一是接入工具调用。给Agent加一个计算器、加一个查时间的函数,让它在你问"今天是几号"时不再凭空推理,而是通过工具拿真实数据。DeepSeek的Function Calling接口写起来不复杂,本质上就是把可用的工具定义成JSON结构发给模型,模型判断需要调用时返回结构化字段,你执行完再回传结果。

第二是加记忆的持久化。现在的记忆是存在内存里的,程序一重启就全丢了。如果你希望Agent能在下次启动时记得你上次聊了什么,需要把消息历史存进SQLite或一个Json文件。手机端这边,把messages数组存进localStorage,刷新页面之后对话不会丢失,这一步改动很小但体验提升巨大。

第三是给手机端加语音输入。现在的移动聊天界面用键盘打字,虽然能用,但不算方便。如果接一个语音转文字的能力,或者利用手机浏览器的语音识别接口,对话体验会更自然。这个方向我还在做,但底子已经打好了——后端只认文本消息,前端加一层"语音转文字"只是个输入源切换。

我个人在完成这个项目后最大的体会是:Agent开发的入门门槛,远没有市面上渲染的那么高。框架是给复杂业务用的,不是给入门者用的。把API调用、上下文管理、工具调用这三个点吃透,你自己就能搭出一个可用的Agent骨架。真正难的不是代码,而是你对"上下文如何流动、模型如何决策"这两个抽象问题的理解深度。把控制台版本多跑几天,多观察对话中的上下文断裂现象,比刷十篇框架教程都管用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询