1. 从零起步:为什么第一天不急着写代码
很多人对"AI应用开发"这四个字的第一反应是:得先学完机器学习、深度学习、Transformer架构,把数学基础打牢,然后才有资格碰。这个想法不能说错,但它把顺序搞反了。我见过太多人卡在"准备阶段"——买了一堆课,看了三章线性代数,然后就没有然后了。真正能让人坚持下去的路径恰恰相反:先用最小的成本跑通一个能看见效果的完整链路,拿到正反馈,再回头补底层原理。
Day1的目标不是"学会AI开发",而是"让一个AI应用在你自己的电脑上跑起来"。这个区别很关键。前者是一个需要几个月甚至几年的长期工程,后者是一个两小时内可以完成的具体任务。我们要做的就是后者。
具体做什么?一个命令行聊天机器人。你在终端里输入一句话,它调用大模型API返回回答,打印在屏幕上。没有界面,没有数据库,没有部署,就是最纯粹的"输入-请求-输出"闭环。为什么选这个作为起点?因为它把AI应用开发中最核心的三个环节压缩到了最短路径上:环境搭建、API调用、结果处理。这三个环节在后续无论你做多复杂的应用——RAG知识库、Agent工作流、多模态应用——本质上都是在这条链路上做加法。
还有一个现实原因:命令行程序排错最简单。没有前端渲染问题,没有跨域,没有浏览器缓存,出错了就是Python报错,堆栈信息一目了然。对于零基础的人来说,第一天最怕的就是"不知道哪里错了",命令行能把这个变量降到最低。
这篇文章适合谁?如果你满足以下任意一条,接下来的内容就是为你写的:完全没写过Python但想试试AI开发;写过一点Python但不知道怎么接大模型API;接过API但总是遇到各种报错不知道怎么排查。我会把每一步的操作意图、可能踩的坑、以及我实际踩过的坑都写清楚,你照着做就行。
提示:Day1不需要安装GPU驱动、不需要CUDA、不需要本地跑模型。所有推理都在云端完成,你的电脑只要能上网、能跑Python就行。这一点很重要,别被"本地部署大模型"的教程带偏了。
2. Python环境搭建:少装一个东西都可能卡你两小时
2.1 版本选择与安装路径的坑
Python安装这件事,看起来简单,实际上是我见过新手翻车最多的地方。先说版本:选3.10或3.11。为什么不选最新的3.12或3.13?因为部分第三方库的预编译包(wheel)还没跟上最新版本,你可能会遇到"pip install成功但import报错"的诡异情况。3.10和3.11是目前生态兼容性最好的两个版本,主流库全部支持。
下载渠道只有一个:Python官网(python.org)。不要去各种"软件管家"或者第三方下载站,那些捆绑安装包会给你塞一堆没用的东西,还可能改你的系统环境变量。官网下载页面会根据你的操作系统自动推荐对应版本,Windows用户直接点"Download Python 3.11.x"那个大按钮就行。
安装时有一个必须注意的选项:Windows安装界面底部有一个"Add Python to PATH"的勾选框,一定要勾上。这个选项的作用是把Python的可执行文件路径写入系统环境变量,不勾的话你在命令行里敲python会提示"不是内部或外部命令"。如果你已经装完了才发现没勾,不用重装,手动添加环境变量也行,但那个操作对新手来说更麻烦,不如卸了重装一遍来得快。
macOS用户相对省心,官网下载pkg包双击安装即可,PATH会自动配置。但要注意:macOS系统自带的Python版本可能比较老(比如2.7或者3.9),你安装的新版本会以python3命令存在,而不是python。这个区别在后面调用的时候要注意。
安装完成后,打开终端(Windows是cmd或PowerShell,macOS是Terminal),输入:
python --version如果显示Python 3.11.x,说明安装成功。macOS用户如果python没反应,试python3 --version。
2.2 pip与虚拟环境:为什么不能直接全局安装
pip是Python的包管理工具,安装Python时会自动带上。验证一下:
pip --version接下来是一个很多人会跳过但非常重要的步骤:创建虚拟环境。虚拟环境的作用是给每个项目一个独立的包安装空间,项目A装的库和项目B装的库互不干扰。不用的后果是什么?你今天装了一个库的1.0版本,明天另一个项目需要2.0版本,升级之后第一个项目就跑不起来了。这种问题排查起来非常痛苦,因为报错信息往往不会直接告诉你"是版本冲突"。
创建虚拟环境的命令:
python -m venv mybot这会在当前目录下创建一个叫mybot的文件夹,里面包含了一个独立的Python环境。然后激活它:
# Windows mybot\Scripts\activate # macOS / Linux source mybot/bin/activate激活成功后,你的命令行提示符前面会出现(mybot)字样。之后所有pip安装的库都会装在这个虚拟环境里,不会污染系统环境。用完想退出,输入deactivate即可。
注意:每次重新打开终端要使用这个项目时,都需要重新激活虚拟环境。这不是bug,是设计如此。忘了激活就直接pip install,库会装到全局环境里,然后你运行代码时可能报"ModuleNotFoundError",因为虚拟环境里没有。
2.3 安装requests:唯一需要的第三方库
Day1我们只需要一个第三方库:requests。它是Python里最常用的HTTP请求库,用来向大模型API发送请求。安装命令:
pip install requests就这一条。不需要装openai SDK,不需要装langchain,不需要装任何框架。为什么?因为我想让你看清楚API调用的本质——无非就是构造一个HTTP POST请求,把JSON数据发出去,再把JSON响应解析回来。框架帮你封装了这些步骤,但如果你不知道底层发生了什么,出了问题你根本不知道怎么排查。
安装完成后验证一下:
python -c "import requests; print(requests.__version__)"能打印出版本号就说明没问题。如果报错,大概率是虚拟环境没激活,或者pip版本太老需要先pip install --upgrade pip。
3. 大模型API调用:从构造请求到解析响应
3.1 API Key的获取与安全存放
要调用大模型API,你需要一个API Key。以DeepSeek为例,注册账号后在控制台的API管理页面可以创建一个Key。这个Key是一串长字符串,相当于你的身份凭证,每次请求都要带上它。
关于API Key的安全,有一条铁律:绝对不要硬编码在代码里,更不要提交到代码仓库。我见过有人把Key直接写在.py文件里然后传到公开仓库,几个小时后收到账单才发现被人盗刷了。正确的做法是存在环境变量里,代码从环境变量读取。
Windows设置环境变量:
setx DEEPSEEK_API_KEY "你的key"macOS/Linux:
export DEEPSEEK_API_KEY="你的key"setx设置后需要重新打开终端才生效。macOS的export只对当前终端会话有效,想永久生效需要写进~/.bashrc或~/.zshrc。
代码里这样读取:
import os api_key = os.environ.get("DEEPSEEK_API_KEY") if not api_key: raise ValueError("请先设置 DEEPSEEK_API_KEY 环境变量")这个检查看起来多余,但实际上能帮你省很多时间。如果Key没设置好,程序会立刻告诉你"环境变量没找到",而不是发一个请求出去然后收到一个模糊的401错误。
3.2 请求体构造:每个参数到底在控制什么
DeepSeek的API接口和OpenAI的格式兼容,请求地址是https://api.deepseek.com/chat/completions。一个最简的请求体长这样:
import requests import os url = "https://api.deepseek.com/chat/completions" headers = { "Authorization": f"Bearer {os.environ.get('DEEPSEEK_API_KEY')}", "Content-Type": "application/json" } data = { "model": "deepseek-chat", "messages": [ {"role": "system", "content": "你是一个简洁的助手,回答控制在三句话以内。"}, {"role": "user", "content": "什么是API?"} ], "temperature": 0.7, "max_tokens": 500 } response = requests.post(url, headers=headers, json=data, timeout=30) result = response.json() print(result["choices"][0]["message"]["content"])逐项拆解这些参数的含义,因为不理解它们你就没法调优:
model指定用哪个模型。DeepSeek目前有deepseek-chat(通用对话)和deepseek-reasoner(推理增强)两个主要选项。Day1用deepseek-chat就够了,响应快、价格低。
messages是一个列表,包含对话历史。每条消息有role和content两个字段。role有三种:system用来设定AI的行为风格,user是用户的输入,assistant是AI之前的回复。多轮对话的原理就是把这个列表越拼越长,每次把完整历史发给API。这一点很关键——API本身是无状态的,它不记得你上一句说了什么,所谓"记忆"完全是客户端在维护。
temperature控制输出的随机性,范围0到2。值越低输出越确定、越保守;值越高越有创造性但也越容易跑偏。写代码、做翻译这类任务建议0.2到0.5,创意写作可以0.8到1.2。Day1聊天机器人设0.7是个中庸的选择。
max_tokens限制回复的最大长度。注意这不是"字数",而是token数。一个中文字大约对应1到2个token,英文一个单词大约1到1.5个token。设500大概能容纳300到400个中文字。设太小会导致回复被截断,设太大浪费额度(虽然不一定会用完,但有些API会按最大值预留计费)。
3.3 响应解析与错误处理
API返回的是一个JSON对象,结构大致如下:
{ "choices": [ { "message": { "role": "assistant", "content": "API是应用程序编程接口..." }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 25, "completion_tokens": 48, "total_tokens": 73 } }取回复内容就是result["choices"][0]["message"]["content"]。usage字段告诉你这次请求消耗了多少token,方便你估算成本。
但实际运行中,你不会每次都拿到正常响应。最常见的错误是429——请求频率超限。这个错误的完整信息通常是exceeded retry limit, last status: 429 too many requests。意思是你在短时间内发了太多请求,触发了服务端的限流。解决办法有两个:一是降低请求频率,在每次请求之间加time.sleep(1);二是实现重试逻辑,收到429后等几秒再试。
import time def chat(messages, retries=3): for i in range(retries): try: response = requests.post(url, headers=headers, json={ "model": "deepseek-chat", "messages": messages, "temperature": 0.7 }, timeout=30) if response.status_code == 429: wait = 2 ** i print(f"请求太频繁,等待{wait}秒后重试...") time.sleep(wait) continue response.raise_for_status() return response.json()["choices"][0]["message"]["content"] except requests.exceptions.Timeout: print(f"请求超时,第{i+1}次重试...") time.sleep(2) except requests.exceptions.RequestException as e: print(f"请求出错:{e}") return None return None这段重试逻辑用了指数退避——第一次等1秒,第二次等2秒,第三次等4秒。为什么用指数而不是固定间隔?因为如果是服务端整体拥堵,固定间隔重试只会加剧拥堵,指数退避给服务端恢复的时间,也提高你重试成功的概率。
4. 命令行聊天机器人:把零件组装成完整程序
4.1 对话循环的设计思路
有了API调用的基础,聊天机器人的逻辑就很直接了:一个无限循环,每次读取用户输入,追加到消息列表,发送请求,打印回复,然后等下一次输入。
import os import time import requests API_KEY = os.environ.get("DEEPSEEK_API_KEY") URL = "https://api.deepseek.com/chat/completions" HEADERS = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" } def chat(messages): data = { "model": "deepseek-chat", "messages": messages, "temperature": 0.7, "max_tokens": 1000 } for i in range(3): try: resp = requests.post(URL, headers=HEADERS, json=data, timeout=30) if resp.status_code == 429: time.sleep(2 ** i) continue resp.raise_for_status() return resp.json()["choices"][0]["message"]["content"] except requests.exceptions.Timeout: print("超时,重试中...") time.sleep(2) return "抱歉,请求失败了,请稍后再试。" def main(): messages = [ {"role": "system", "content": "你是一个友好的助手,用简洁的中文回答问题。"} ] print("聊天机器人已启动,输入 quit 退出。") while True: user_input = input("\n你:").strip() if not user_input: continue if user_input.lower() in ("quit", "exit", "q"): print("再见!") break messages.append({"role": "user", "content": user_input}) reply = chat(messages) print(f"\nAI:{reply}") messages.append({"role": "assistant", "content": reply}) if __name__ == "__main__": main()这段代码有几个设计决策值得说明。
为什么把system消息放在循环外面?因为system消息只需要设置一次,它定义了AI的全局行为。如果放在循环里每次都重新构造,虽然效果一样但代码更啰嗦。
为什么要把AI的回复也追加到messages里?这就是多轮对话的实现方式。每次请求都把完整的对话历史发过去,AI才能"看到"之前聊了什么。代价是token消耗会随着对话轮次增加而增长,聊得越久每次请求越贵。Day1不用管这个,但心里要有数。
为什么用strip()处理输入?用户可能不小心多敲了空格或回车,strip()去掉首尾空白字符。如果输入为空就跳过,避免发一个空消息给API浪费额度。
4.2 实测中会遇到的问题
第一次跑起来大概率不会一帆风顺。以下是我实际遇到过的情况和解决办法。
中文乱码。Windows的cmd默认编码可能是GBK,而API返回的是UTF-8,打印中文时可能出现乱码。解决办法是在代码开头加:
import sys sys.stdout.reconfigure(encoding='utf-8')或者在运行前执行chcp 65001切换cmd的编码页。
响应特别慢。大模型生成回复需要时间,尤其是回复较长时,等十几秒是正常的。timeout=30设的是30秒超时,如果网络状况不好可能需要调到60。但注意,超时时间设太长也不好——如果请求真的卡死了,你要等很久才知道。我的经验是30到60秒比较合理。
Key无效或余额不足。如果返回401,检查Key是否正确、是否有多余的空格。如果返回402或类似错误,检查账户余额。这些错误信息API都会返回,在response.json()里能看到具体的错误描述,调试时把完整的响应打印出来看。
对话越来越慢。聊了十几轮之后,每次请求的token数会显著增加,响应变慢、费用上升。Day1的简单实现没有做历史截断,实际项目中需要限制历史消息的数量或者做摘要压缩。这是后续要解决的问题,第一天知道有这回事就行。
4.3 从能跑到好用:几个立竿见影的改进
程序跑通之后,可以做一些小改进让它更好用。
流式输出。默认情况下,AI生成完整回复后才一次性返回,你要盯着空屏幕等好几秒。流式输出可以让文字像打字机一样逐字出现,体验好很多。实现方式是在请求体里加"stream": True,然后逐行读取响应:
def chat_stream(messages): data = { "model": "deepseek-chat", "messages": messages, "temperature": 0.7, "stream": True } resp = requests.post(URL, headers=HEADERS, json=data, stream=True, timeout=60) full_reply = "" for line in resp.iter_lines(): if not line: continue line = line.decode("utf-8") if line.startswith("data: "): payload = line[6:] if payload == "[DONE]": break import json chunk = json.loads(payload) delta = chunk["choices"][0]["delta"].get("content", "") print(delta, end="", flush=True) full_reply += delta print() return full_reply流式输出的原理是服务端每生成一小段文字就推送给客户端,而不是等全部生成完再发。iter_lines()逐行读取响应,每行以data:开头,最后以data: [DONE]结束。这个格式和OpenAI的流式接口一致。
保存对话记录。把messages列表在退出时写入一个JSON文件,下次启动时读回来,就能实现跨会话的记忆。这个功能实现简单但很实用:
import json def save_history(messages, filename="history.json"): with open(filename, "w", encoding="utf-8") as f: json.dump(messages, f, ensure_ascii=False, indent=2) def load_history(filename="history.json"): try: with open(filename, "r", encoding="utf-8") as f: return json.load(f) except FileNotFoundError: return [{"role": "system", "content": "你是一个友好的助手。"}]加一个清空历史的命令。在循环里判断如果用户输入/clear,就把messages重置为只含system消息的初始状态。这样聊到一半想换话题时不用重启程序。
5. 第一天之后:这条路接下来怎么走
Day1跑通了一个命令行聊天机器人,代码量不到一百行,但它包含了一个AI应用的全部核心要素:身份认证、请求构造、响应解析、错误处理、多轮对话管理。后面无论你做什么方向的应用,都是在这个骨架上长肉。
如果你想继续深入,几个自然的下一步:给机器人加上工具调用能力,让它能查天气、算数学、搜索网页;把命令行换成Web界面,用Flask或FastAPI做一个简单的聊天页面;接入向量数据库做RAG,让机器人能回答你私有文档里的问题。每一条路都建立在你今天搭好的这个基础之上。
我在带新人的时候经常说一句话:第一天最重要的不是学会了什么,而是确认了"我能让它跑起来"。这个确认本身就是最大的收获。很多人卡在第一步不是因为技术难,而是因为不确定自己走的方向对不对。跑通一个最小闭环,你就有了一个可以不断迭代的起点。
最后分享一个我自己的习惯:每次开始一个新项目,第一件事就是写一个能跑通全链路的最小版本,哪怕它只输出"hello world"。这个版本可能丑、可能慢、可能功能残缺,但它能跑。有了能跑的版本,后面所有的改进都有参照物,你知道改坏了可以退回来。没有能跑的版本,你就是在黑暗中摸索,每一步都不确定对不对。Day1的聊天机器人就是这样一个最小可运行版本,把它保存好,后面你会经常回来参考它。