青戈AI小栈
会员限时8折
大模型应用开发基础

第一次调用大模型

6,895 字约 18 分钟读完61 次阅读更新于 2026/10/20

登录后收藏

阅读设置

字号
行距
代码

在 DeepSeek 网页里输入问题,就能收到模型的回答。要让自己的 Python 程序也能使用模型,需要通过模型服务提供的 API 发送消息,再读取返回的回复,供程序继续处理。

本课程围绕一个“任务助手”展开:先让程序调用模型回答问题,再逐步加入多轮对话、查询和新增待办任务等功能,学习怎样把模型接入自己的应用。

本节结束时,你能用 Python 向 DeepSeek 发送问题,并在终端看到模型的回复。这里只需要 Python 基础和终端,不需要提前准备任务管理项目。

准备 API Key

程序访问 DeepSeek 的模型服务时,需要提供一个凭据,让服务识别调用来自哪个账户,并记录相应的用量。这个凭据就是 API Key。

打开 DeepSeek 开放平台,注册或登录,在 API keys 页面创建密钥并妥善保存。网页聊天与 API 调用是两个使用入口;运行本节程序前,在开放平台核对账户余额和 API 计费规则。调用会消耗额度,具体价格见 官方模型与价格。

密钥相当于程序访问账户的凭据,不要发到群里、写进截图或提交到 Git。后面会把它放在本机的 .env 文件中,代码只读取配置。

创建独立项目

需要 Python 3.12 或以上版本。本节 Windows 命令使用 PowerShell,先在准备存放项目的目录中执行:

powershell
mkdir task-assistant
cd task-assistant
py --version
py -m venv .venv

先核对 py --version 显示的版本满足要求。后面的命令直接使用虚拟环境里的解释器,不依赖激活脚本;即使 PowerShell 不允许运行 Activate.ps1,也能继续。

macOS / Linux 创建目录的命令相同,把 py 换成 python3。后文 Windows 命令中的 .\.venv\Scripts\python.exe 换成 .venv/bin/python。

在 task-assistant/ 根目录新建 requirements.txt:

文件
openai==3.35.1
python-dotenv==1.2.4

openai 是封装了模型接口请求的 SDK,负责发送 HTTP 请求、解析响应,省去手工组装请求头和 JSON 的重复工作。模型运行在 DeepSeek 的服务器上,本机安装的 SDK 负责与它通信。

DeepSeek 提供了与 OpenAI 兼容的接口,所以这里可以使用 OpenAI SDK;配置 DeepSeek 的地址和密钥后,请求仍发给 DeepSeek。

python-dotenv 用来读取 .env 文件。本节固定这两个直接依赖,间接依赖由安装工具解析;固定直接依赖不等于锁定全部依赖。

保存文件,在项目根目录执行:

powershell
.\.venv\Scripts\python.exe -m pip install -r requirements.txt

把地址、密钥和模型名放进配置

在项目根目录新建 .env,写入:

dotenv
LLM_API_KEY=替换成自己的DeepSeek密钥
LLM_BASE_URL=https://api.deepseek.com
LLM_MODEL=deepseek-flash

第一行等号后必须替换为真实密钥,不保留占位文字。确认文件名就是 .env,没有被编辑器保存成 .env.txt。

三个配置分别回答:凭什么访问、请求发到哪里、使用哪个模型。地址写服务的基础地址,不要再拼上 /chat/completions,SDK 会补齐接口路径。

本节使用 deepseek-flash 模型。DeepSeek 支持的模型及对应名称见 官方模型列表。

同一目录再新建 .gitignore:

gitignore
.env
.venv/
__pycache__/

它让 Git 忽略本机密钥、虚拟环境与缓存。如果密钥已经被提交过,后来增加忽略规则不能撤回泄露,应先在平台撤销旧密钥,再创建新的密钥。

发出第一次请求

环境变量按名称保存字符串,程序可以读取它们作为配置。本例用 load_dotenv() 把 .env 中的配置加载到当前程序的环境变量,再用 os.getenv() 按名称取值,具体用法见 python-dotenv 官方说明(英文)。

在项目根目录新建 main.py,写入完整代码:

python
import os
from pathlib import Path

from dotenv import load_dotenv
from openai import OpenAI


# 指定 main.py 旁边的 .env,避免运行目录变化时读取其他配置文件。
load_dotenv(Path(__file__).with_name(".env"))

for name in ("LLM_API_KEY", "LLM_BASE_URL", "LLM_MODEL"):
    # 缺少任一配置就停止并指出字段,避免发出无效请求。
    if not (os.getenv(name) or "").strip():
        raise ValueError(f"请在 .env 或环境变量中填写 {name}")

with OpenAI(
    api_key=os.getenv("LLM_API_KEY"),
    base_url=os.getenv("LLM_BASE_URL"),
) as client:
    response = client.chat.completions.create(
        model=os.getenv("LLM_MODEL"),
        messages=[
            {"role": "user", "content": "用一句话说明你能怎样帮助我整理待办任务。"}
        ],
        # DeepSeek 默认开启思考模式;本例关闭它,先完成普通文本问答。
        extra_body={"thinking": {"type": "disabled"}},
    )

print(response.choices[0].message.content)

Path(__file__).with_name(".env") 指向 main.py 旁边的 .env。load_dotenv() 读取这份文件,os.getenv("LLM_MODEL") 等调用再取出配置值。默认情况下,终端中已有的同名环境变量优先,不会被 .env 覆盖。

for 循环逐项检查三个必填配置。未设置的变量会让 os.getenv() 返回 None,or "" 将它转成空字符串,再用 .strip() 排除纯空白。检查不通过就指出具体缺少哪一项,在发送请求前停止。

OpenAI(...) 根据密钥和基础地址创建客户端,此时还没有调用模型。with ... as client 把这个客户端绑定到变量 client,供缩进代码块中的代码使用;离开代码块时自动关闭它。真正发请求的是 client.chat.completions.create(...),返回值 response 是 SDK 解析后的响应对象。

messages 是本次发送的消息列表。这里只有一条消息:role="user" 表示用户发言,content 是问题文字。下一节再加入不同角色和对话历史。

extra_body 把服务商扩展的字段放进请求 JSON。这里用 DeepSeek 的 thinking 开关关闭思考模式,先完成普通文本问答。这个字段是服务商扩展,不能假设所有兼容服务都支持,模式说明见 DeepSeek 思考模式。

最后一行的 response.choices[0].message.content 从响应中取出第一条候选回复的正文,交给 print() 打印。先用它看到模型的回答,后面再展开查看响应的其他字段。

在项目根目录运行:

powershell
.\.venv\Scripts\python.exe main.py

也可以先激活虚拟环境,再简写运行命令。在项目根目录执行 .\.venv\Scripts\Activate.ps1 后,当前 PowerShell 终端可直接用 py main.py 或 python main.py。macOS / Linux 用 source .venv/bin/activate 激活,再用 python main.py。

同一个终端不必重复激活,新开终端需要重新激活。激活失败时,继续使用前面的完整解释器路径即可。

本例会等待完整响应返回后再打印回复,等待期间不会逐字输出,终端暂时没有新文字不代表程序已经卡死。如果等待过久,可以在运行程序的终端按 Ctrl+C 中止本次运行;回到命令提示符后,再检查网络和服务状态。中止本机程序不保证服务端也已取消请求,不要把它当成撤销本次用量。

请求完成后,终端应显示一段模型回复,例如:

输出
我可以帮你把零散的待办事项整理成清晰的任务清单。

这是回复形式的示例,措辞不要求一致。此时程序只发送了一段文字,没有连接任务 API;模型说“可以整理”不代表已经创建了任务。

为什么用 with 管理客户端

客户端需要管理底层的 HTTP 连接。with 利用客户端的上下文管理器自动完成清理。

  • 请求放在 with 内。 离开代码块后客户端已关闭,不能继续用它发送请求。
  • 异常时也会关闭连接。 请求正常返回或抛出异常,退出代码块时都会调用 close();请求异常仍会向外抛出。
  • 读取已返回的 response 可以放在外面。 本例等完整响应返回后才退出代码块,关闭连接不会删除响应对象,所以最后的 print() 无需缩进。

回复文字只是响应的一部分

保留 main.py 的现有内容,在最后追加:

python
print("模型:", response.model)
print("结束原因:", response.choices[0].finish_reason)
print("用量:", response.usage)
print("完整响应:")
print(response.model_dump_json(indent=2))

再次运行同一条命令。每次运行都会重新发送请求,并产生相应的 API 用量。model_dump_json(indent=2) 把响应对象转成缩进后的 JSON 文本,方便查看层级。

完整响应通常比回复文字长得多。下面只示意本节要找的字段,省略其他字段;数字和回复是示例,不是这次请求的固定结果:

json
{
  "model": "deepseek-flash",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "我可以帮你把零散的待办事项整理成清晰的任务清单。"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 20,
    "completion_tokens": 18,
    "total_tokens": 38
  }
}

按这个层级读 response.choices[0].message.content:先取候选回复列表 choices,再取第一项 [0],进入消息 message,最后取正文 content。response 不是字典,直接写 response["choices"] 不是这套 SDK 的属性读取方式;需要字典时可用 response.model_dump()。

message.role 的 assistant 表示这是助手的回答。finish_reason 说明本次生成为什么结束:stop 表示正常停止,length 表示达到长度上限,不能把后一种情况当成完整回答。字段定义可查 Chat Completions API。

usage 是本次调用的用量。Token 是模型处理文本的计量单位,不等于字数;prompt_tokens 对应输入,completion_tokens 对应输出,total_tokens 是总量。第 03 节再根据这些字段计算费用。本节先确认能找到它们,不用凭中文字数推算账单。

为什么用 OpenAI SDK 调 DeepSeek

包名叫 openai,不代表请求一定发送给 OpenAI。决定请求目标的是客户端的 base_url,当前填写的是 DeepSeek 地址,使用的也是 DeepSeek 密钥。

OpenAI 兼容接口指服务采用与 OpenAI 接口相近的请求和响应格式,因此可以复用同一套 SDK。它不表示各家的模型名、参数和能力完全相同。

换到另一家兼容服务时,通常需要一起核对密钥、基础地址、模型名,并检查代码是否使用了服务商专属字段。例如本节的 thinking 就需要重新核对。只在 DeepSeek 当前支持的模型之间切换时,可以保留这段代码,只修改 .env 的 LLM_MODEL。

调用失败时从哪里检查

运行失败的信息显示在执行 main.py 的终端中。出现 Python 异常堆栈时,先看最后一行,再根据异常类型定位。

提示“请在 .env 或环境变量中填写 LLM_API_KEY”等配置错误:这是程序在本机做的检查,还没发请求。检查 .env 是否和 main.py 放在一起、变量名是否一致、等号后是否有值,再保存并重跑。

修改 .env 后,模型名仍没有变化:先保存文件并重新运行程序。如果当前终端已经设置了 LLM_MODEL,它会优先于 .env。在该 PowerShell 终端执行 Remove-Item Env:LLM_MODEL,macOS / Linux 执行 unset LLM_MODEL,清除当前终端中的值后再运行。密钥或地址修改不生效时,也按同样方式检查对应的环境变量。

出现 AuthenticationError,状态码为 401:按 官方错误码说明,这表示身份验证失败。核对 .env 中是否仍是占位文字,密钥是否来自 DeepSeek 开放平台、是否已被撤销。不要把完整密钥贴到报错求助信息里。

出现 APIStatusError,状态码为 402:DeepSeek 返回的 402 表示账户余额不足。到开放平台检查当前密钥所属账户的 API 余额,补足额度后再运行。

出现 APIConnectionError:检查 LLM_BASE_URL 的拼写、网络和代理配置。它表示连接层面失败,不能直接认定是密钥错误。

出现 APITimeoutError:请求在等待连接、响应或传输数据时超过了超时限制,不等同于密钥错误。先检查网络、代理和模型服务状态,恢复后重新运行。本节固定的 openai==3.35.1 默认读取超时为 600 秒(10 分钟),对超时等部分错误默认最多自动重试2 次,加上首次请求可能共尝试 3 次,所以报错前可能等待很久。读取超时是读取数据时的等待限制,不是整个调用的总时限。不想继续等待时,可按 Ctrl+C 中止;第 08 节再设置超时时间和重试次数。

出现 ModuleNotFoundError,找不到 openai 或 dotenv:确认安装和运行使用同一个虚拟环境解释器。在项目根目录执行以下完整命令,重新安装依赖并运行:

powershell
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
.\.venv\Scripts\python.exe main.py

结果核对

  • 终端能打印模型回复,但任务列表不会因为这次调用发生变化。
  • 在完整响应中找到 model、choices、message、finish_reason 和 usage,能对应到代码中的属性读取位置。
  • 知道本例等待期间不会逐字输出,等待过久时能按 Ctrl+C 中止。
  • 能区分 .env 缺配置的本机错误、401 身份验证失败、连接失败与超时,知道分别从哪里排查。

下一节连续发送两次消息,观察为什么单独的一次模型调用不会自动带上上一次对话。

练习

保持 main.py 不变,只修改 .env 中的 LLM_MODEL,把第一次调用换成 DeepSeek 当前支持的另一个对话模型,再运行一次。对照两次打印的 model 和 usage,最后把配置恢复为正文使用的模型。

验收:两次都能获得回复;返回的 model 字段不同;main.py 没有改动;能在各自的响应中找到输入与输出 token。回复措辞是否不同不作为切换成功的依据。模型可能有别名,以服务实际返回的标识为准;选择官方列表中的两个不同模型,不用同一模型的两个别名做对照。

查看提示

先查正文链接的官方模型列表,找另一个仍可调用的对话模型。地址与密钥仍属于同一家服务,不需要修改;每次保存 .env 后重新运行程序,观察打印的响应字段,不靠回答口吻猜模型。如果配置没有生效,回看正文中环境变量的优先级与对应排错说明。

查看参考答案

先保持正文配置,运行一次 main.py,记录返回的 model 和 usage。这次把模型从 deepseek-flash 换成 deepseek-v4-pro,继续使用原有的 DeepSeek 密钥和基础地址。

第一次 .env 中的模型行是:

dotenv
LLM_MODEL=deepseek-flash

保存首次输出后,只把这一行替换为:

dotenv
LLM_MODEL=deepseek-v4-pro

保留 .env 中原有的密钥与基础地址,不要用上面的单行片段覆盖整个文件。在 task-assistant/ 根目录再次运行:

powershell
.\.venv\Scripts\python.exe main.py

macOS / Linux 使用 .venv/bin/python main.py。对照两次输出中的 模型: 和完整响应的 model,确认返回标识分别对应两个模型,再找到各自 usage 中的 prompt_tokens、completion_tokens 和 total_tokens。输入相同不保证 token 用量相同,也不保证两个模型会写出不同的句子。

代码不变仍能切换,是因为每次启动程序,load_dotenv() 都会加载配置,请求通过 os.getenv("LLM_MODEL") 读取模型名。如果当前终端已经设置了同名环境变量,默认会优先使用它;按正文排错说明清除当前终端中的 LLM_MODEL 后,再运行程序。

最后把 .env 中的 LLM_MODEL 恢复成 deepseek-flash 并保存,供下一节继续使用。

有帮助
登录后提问 / 纠错

提问与反馈

仅你和管理员可见。我的反馈

设置你的昵称

在英雄榜和讨论中,使用你自己的名字。

2–12 个字符0 / 12