作为 Java 程序员,你肯定调过不下几百个 HTTP 接口吧?发个 JSON,收个 JSON,熟得不能再熟了。
那你有没有想过——大模型,本质上也就是一个 HTTPS JSON 接口而已。你发一个消息数组过去,它回一段文本给你。
有人可能会问:"那为什么不直接用 Java 写?" 问得好。因为 Python 的 openai 库是这套协议的"官方方言",10 行代码就能打通。先用 Python 把整条链路跑通、确认协议没问题,再迁到 Java,排查成本最低。这就跟你用 Postman 调通接口再写代码是一个道理。
今天的目标很明确:安装 SDK → 跑通非流式 → 跑通流式 → 看懂请求响应字段 → 为明天的 Spring Boot 封装打底。
▲ API 调用流程:请求-响应模式
一、环境准备:5分钟搭好调用环境
我们先把环境搞定。Python 3.10+ 就行,安装两个包:openai 是官方 SDK,python-dotenv 用来读环境变量。
pip install openai python-dotenv
然后建一个简单的项目结构:
day99-first-call/├── .env # 密钥,不要提交 Git├── .gitignore└── chat_demo.py
.gitignore 里记得把 .env 加上,密钥绝不能进 Git。
.env 配置(以 DeepSeek 为例,国内可直接用):
# 任选一家,协议相同,只换 KEY 和 BASE_URLLLM_API_KEY=sk-你的密钥LLM_BASE_URL=https://api.deepseek.comLLM_MODEL=deepseek-chat
密钥只放环境变量或 .env 文件,永远不要写进代码、截图、Git 仓库。这和数据库密码是同一条红线,踩了就是事故 [裂开]
各家兼容地址速查:
DeepSeek 用 https://api.deepseek.com,
通义千问用 https://dashscope.aliyuncs.com/compatible-mode/v1,
智谱用 https://open.bigmodel.cn/api/paas/v4/,
OpenAI 用 https://api.openai.com/v1。
协议都一样,换 KEY 和地址就行。
二、最小可运行示例:非流式调用
好了,环境搭完了。我们来看最简单的调用方式——非流式。说白了就是发一次请求,等模型全部生成完了,一次性把结果返回给你。这就跟你调普通 HTTP 接口一模一样。
importosfromdotenvimportload_dotenvfromopenaiimportOpenAIload_dotenv()client=OpenAI(api_key=os.getenv("LLM_API_KEY"),base_url=os.getenv("LLM_BASE_URL"),)resp=client.chat.completions.create(model=os.getenv("LLM_MODEL"),messages=[{"role": "system", "content": "你是资深 Java 架构师,回答简洁、可落地。"},{"role": "user", "content": "用一句话解释 Spring 的依赖注入。"},],temperature=0.2,)print(resp.choices[0].message.content)print("---")print("model:", resp.model)print("usage:", resp.usage)
运行 python chat_demo.py,如果终端打出一段中文解释,并带上 prompt_tokens / completion_tokens,那就成功了!
▲ messages 数组的三角色结构
大家注意,SDK 帮我们做了很多事,但它底层发的就是一笔普通的 HTTP POST 请求。我们来看原始请求体长什么样:
POST /v1/chat/completionsAuthorization: Bearer sk-xxxContent-Type: application/json{"model": "deepseek-chat","messages": [{"role": "system", "content": "..."},{"role": "user", "content": "..."}]}
再看响应体,几个关键字段大家要记牢:
1. choices[0].message.content —— 真正的回答内容,相当于接口返回的 data
2. finish_reason=stop —— 正常结束,相当于 HTTP 200
3. finish_reason=length —— 被 max_tokens 截断了,类似分页没拿全
4. usage.total_tokens —— 计费依据,相当于调用量监控
三、流式响应:打字机效果怎么实现
非流式调用虽然简单,但有个大问题:等整段生成完才返回。如果回答很长,用户盯着空白屏幕半天没反应,体验极差 [捂脸]
所以聊天类产品几乎都用流式响应——服务端一边生成一边推,前端逐字显示,就像打字机一样。底层协议就是 SSE(Server-Sent Events)。
▲ 流式 vs 非流式:首字延迟的天壤之别
代码改动非常小,就加一个参数 stream=True:
stream=client.chat.completions.create(model=os.getenv("LLM_MODEL"),messages=[{"role": "user", "content": "用三点说明 REST 和 RPC 的区别。"}],stream=True,)forchunkinstream:delta=chunk.choices[0].delta.contentifdelta:print(delta, end="", flush=True)print()
流式时每个 chunk 只有增量 delta.content,没有完整的 message,也通常没有 usage(部分厂商会在最后一个空 chunk 带上)。千万别用非流式的方式去读流式的返回,不然会报空指针哦 [笑哭]
后面 DAY104 我们会用 Spring 的 SseEmitter 把同样的协议接到浏览器前端。今天先用 Python 感受一下效果。
四、封装成可复用函数
大家注意,实际项目里可不能每次调用都把 Key、URL、异常处理散落在业务代码里。这跟 Java 里你不会每次发 HTTP 都 new 一个 HttpClient 是一个道理。
我们做一个最小封装,一个函数同时支持流式和非流式:
fromtypingimportIteratordefchat(messages: list[dict], stream: bool=False)->str|Iterator[str]:kwargs=dict(model=os.getenv("LLM_MODEL"),messages=messages,temperature=0.2,stream=stream,)ifstream:defgen():forchunkinclient.chat.completions.create(**kwargs):piece=chunk.choices[0].delta.contentifpiece:yieldpiecereturngen()resp=client.chat.completions.create(**kwargs)returnresp.choices[0].message.content
这个函数就是明天 Java 里 LlmClient 的原型:一个方法进 messages,一个方法出文本或流。理解了这个封装思路,明天切到 Java 就是换个语法的事儿。
五、必做的错误处理与常见踩坑
写代码哪有不报错的 [笑哭] 这里我把常见的坑都给大家列出来,省得你一个一个踩。
1. 401 未授权 —— 最常见,Key 错了或者环境变量没加载。先检查 .env 文件有没有被正确加载,打印一下 os.getenv("LLM_API_KEY") 看看。
2. 429 限流/余额不足 —— 要么是调用太频繁触发了速率限制,要么就是账户没钱了。遇到这个要做退避重试,千万别死循环地打,会被封 IP 的。
3. timeout 超时 —— 大模型生成有时候确实慢,尤其是长文本。把超时调到 60 秒以上,或者干脆用流式,更稳。
4. 空 content —— 可能触发了安全策略被拦截,也可能是走了工具调用模式。别直接取 content 判空,先打印完整的 message 再判断。
5. 中文乱码 —— Windows 控制台的老问题了,执行 chcp 65001 切到 UTF-8 编码就行。
给大家一个标准的异常处理模板:
fromopenaiimportAPIStatusError, APITimeoutErrortry:text=chat([{"role": "user", "content": "ping"}])print(text)exceptAPITimeoutError:print("超时:加大 timeout 或改用流式")exceptAPIStatusErrorase:print(f"HTTP {e.status_code}: {e.message}")
1. base_url 多写或少写 /v1DeepSeek 用 https://api.deepseek.com 即可,SDK 会自动补路径;有的厂商要求带 /v1。报 404 先查这个。
2. 模型名写错比如 gpt-3.5-turbo 在 DeepSeek 上不存在,要用 deepseek-chat。
3. 把流式当非流式读stream=True 时 resp.choices[0].message 不存在,拿到的是 delta。
4. temperature 乱开代码生成、代码审查用 0.1~0.3(确定性高);头脑风暴、创意写作再用 0.7+(随机性高)。
今日小结
1. 大模型调用 = OpenAI 兼容的 Chat Completions 接口
2. 核心数据结构是 messages[] 数组,三角色:system / user / assistant
3. 非流式拿完整字符串,流式拿增量 token(打字机效果)
4. Python 只是验证协议;真正的业务系统明天用 Java 写
今日练习任务
任务 1:把同一句提示分别打给 DeepSeek 和通义(只改 .env),对比回答风格和耗时。感受一下不同模型的差异。
任务 2:用"资深 Java 架构师"的 System Prompt 让模型审查一段代码,要求输出 JSON 格式:{ "issues": [], "suggestion": "" }。体会一下结构化输出怎么玩。
任务 3:把非流式和流式各跑一次,用秒表感受一下首字延迟的差异。不用精确计时,体感就行 [狗头]
🔮 下节课预告
用 Spring Boot + RestClient 封装同一套 API,对外暴露 /chat 接口。