模型 API、密钥与环境变量:第一次请求之前要检查什么
正在读取学习记录…
模型 API 是程序向模型服务发送请求并取得响应的接口。一次调用至少要知道服务地址、模型名以及如何证明调用者身份;密钥就是这类身份凭据之一。配置检查器先读取这些值,确认程序准备发往哪里,再把通过检查的配置交给请求代码。
这里先用教学用假密钥跑通一次本地配置检查,不需要真实模型账号。看懂正常配置从哪里来、流向哪里之后,再故意留空模型名或写错地址,观察错误怎样在发出请求前被发现。
需要回查请求和等待机制时,可以打开 HTTP、REST 与 JSON和异步与超时。
0. 一次请求的配置流向
Agent 的推理循环可能连续调用模型多次,还可能把工具结果和企业资料发送给供应商。一个普通脚本里的错误端点、泄露密钥或无上限重试,会被循环放大成数据和费用事故。因此第一次请求之前,要先把“配置是否合法”和“真实请求是否获准”拆成两个明确阶段。
1. 一次模型调用究竟依赖什么
至少有四个相互独立的配置:请求发到哪里、由谁付费、调用哪个模型、等多久。把它们混成一个“模型配置字符串”,很容易在切换环境时弄错。
| 配置 | 作用 | 常见误解 |
|---|---|---|
| Base URL | API 服务的基础地址 | 地址包含品牌名就一定可信 |
| API Key | 供应商识别调用身份的凭据 | 有字符串就说明凭据有效 |
| Model | 供应商可识别的模型标识 | 网页聊天产品名就是 API 模型名 |
| Timeout | 客户端等待策略的一部分 | 本地超时就意味着远端停止执行 |
调用还需要 API 路径、请求体格式、权限、可用余额和网络条件。配置通过,只是链路中的第一步。HTTP 成功也不代表返回内容符合业务要求,结构化输出与工具权限会在后续章节继续处理。
DeepSeek 的基础地址、模型名称和请求格式可以查阅官方入门文档。配置时按文档填写对应的地址和模型名称。
2. 用环境变量传入配置
环境变量是进程拿到配置的一种方式。Python 中的 os.environ 是字符串映射,因此 "15" 不会自动变成数字,空字符串也不会自动被视为配置缺失。Python 环境变量文档
子进程通常继承启动它时的环境。你在一个终端设置了变量,已经运行的 Web 服务不会因此自动获得新值;另一个终端也未必能看到它。遇到“我明明设置了”的问题,先确认究竟是哪个进程读取配置、它何时启动。
常见的 .env 文件是本地约定,不是 Python 自动识别的特殊文件。这里代码不会自动加载它。使用加载库时,需要明确文件位置、是否允许覆盖部署环境、加载失败是否阻止启动。生产秘密应由部署平台或秘密管理服务受控注入,并限制读取权限。
不要把全部环境变量打印出来排错。它们可能包含数据库口令、访问令牌和其他服务密钥。排错信息可以记录“某字段是否存在”,不必记录字段值。
3. 为什么密钥必须留在服务端
网页代码、网络请求、浏览器存储都能被访问者检查。把密钥写进前端环境变量,再打包压缩或 Base64 编码,依旧是在把凭据交给用户。字符串变得难读,不等于保密。
正确边界是:浏览器用网站自己的登录身份访问后端,后端完成鉴权、授权、额度和输入限制,再使用供应商密钥发起请求。供应商密钥不能回传给浏览器。
但“放在后端”还不够。如果任何人都能匿名调用这个转发接口,它仍可能成为替别人付费的开放代理。后端需要同时限制用户、频率、并发、输入长度和总费用;不能只隐藏那一串字符。
| 位置 | 适合保存什么 | 不应保存什么 |
|---|---|---|
| Git 仓库 | 配置字段说明、无秘密的示例、校验代码 | 可用密钥、含密钥的截图 |
| 浏览器 | 用户允许的页面状态和短期会话信息 | 供应商长期 API Key |
| 服务端运行环境 | 经授权注入的服务凭据 | 对所有员工可读的明文秘密清单 |
| 普通应用日志 | 请求 ID、状态、耗时、配置版本 | Authorization 头、完整环境、未经处理的敏感正文 |
4. 运行配置检查器
复制下方完整代码,保存为 config.py 和 test_config.py,放在同一个目录。
保存为 config.py:
"""读取并检查模型连接配置。"""
from dataclasses import dataclass, field
import math
import os
@dataclass(frozen=True)
class Config:
base_url: str
model: str
timeout_seconds: float
# 打印配置对象时隐藏密钥字段。
api_key: str = field(repr=False)
def load_config(env):
values={}
for name in ('MODEL_BASE_URL','MODEL_NAME','MODEL_API_KEY','MODEL_TIMEOUT_SECONDS'):
value=env.get(name)
if not isinstance(value,str) or not value.strip():
raise ValueError(name+' is required')
values[name]=value.strip()
# 仅允许配置中列出的服务地址,防止密钥发往其他地址。
if values['MODEL_BASE_URL'] not in ('https://api.deepseek.com','https://api.deepseek.com/v1'):
raise ValueError('MODEL_BASE_URL is not approved')
try:
# 环境变量读出来是字符串,这里转成数字秒数。
timeout=float(values['MODEL_TIMEOUT_SECONDS'])
except ValueError:
raise ValueError('MODEL_TIMEOUT_SECONDS must be numeric') from None
if not math.isfinite(timeout) or not 0 < timeout <= 120:
raise ValueError('MODEL_TIMEOUT_SECONDS must be within (0, 120]')
return Config(values['MODEL_BASE_URL'],values['MODEL_NAME'],timeout,values['MODEL_API_KEY'])
if __name__=='__main__':
try:
config=load_config(os.environ)
except ValueError as error:
print('Configuration rejected:',error)
raise SystemExit(2)
print('Configuration accepted; no request sent; key presence checked, not authenticated.')完整代码:test_config.py
# 核对模型 API、密钥与环境变量的正常结果和错误分支;断言失败时会显示对应的测试。
import unittest
from config import load_config
class ConfigTests(unittest.TestCase):
def env(self):
return {'MODEL_BASE_URL':'https://api.deepseek.com', 'MODEL_NAME':'lesson-model',
'MODEL_API_KEY':'test-secret-not-valid', 'MODEL_TIMEOUT_SECONDS':'15'}
def test_valid_and_redacted(self):
result=load_config(self.env())
self.assertEqual(result.timeout_seconds,15)
self.assertNotIn('test-secret-not-valid',repr(result))
def test_missing_and_blank(self):
for key in self.env():
with self.subTest(key=key):
value=self.env(); value[key]=' '
with self.assertRaises(ValueError): load_config(value)
def test_endpoint_allowlist(self):
for url in ['http://api.deepseek.com','https://api.deepseek.com.evil.invalid',
'https://user:password@api.deepseek.com','https://api.deepseek.com?token=x',
'https://127.0.0.1','https://api.deepseek.com/other']:
with self.subTest(url=url):
value=self.env();value['MODEL_BASE_URL']=url
with self.assertRaises(ValueError):load_config(value)
def test_timeout_bounds(self):
for timeout in ['0','-1','NaN','inf','abc','121']:
value=self.env();value['MODEL_TIMEOUT_SECONDS']=timeout
with self.assertRaises(ValueError):load_config(value)
def test_errors_do_not_echo_secret(self):
value=self.env(); value['MODEL_TIMEOUT_SECONDS']='test-secret-not-valid'
try: load_config(value)
except ValueError as error: self.assertNotIn('test-secret-not-valid',str(error))
else: self.fail('invalid timeout accepted')
if __name__=='__main__': unittest.main()在 PowerShell 中设置下面的示例变量。密钥是假字符串;变量只在当前终端会话生效。
先新建实验文件夹,把两个程序文件放进去,再打开 PowerShell:
pwd
ls
python --versionGet-ChildItem 应看到 config.py 与 test_config.py。下面的 $env:名称 = "值" 只为当前 PowerShell 进程及它随后启动的 Python 子进程设置变量;关闭窗口后通常消失,也不会自动更新已经启动的服务。
$env:MODEL_BASE_URL = "https://api.deepseek.com"
$env:MODEL_NAME = "lesson-model"
$env:MODEL_API_KEY = "test-secret-not-valid"
$env:MODEL_TIMEOUT_SECONDS = "15"
python -B -X utf8 config.py
$LASTEXITCODEmacOS 终端命令
export MODEL_BASE_URL="https://api.deepseek.com"
export MODEL_NAME="lesson-model"
export MODEL_API_KEY="test-secret-not-valid"
export MODEL_TIMEOUT_SECONDS="15"
python -B -X utf8 config.py
echo $?配置检查预期输出:
Configuration accepted; no request sent; key presence checked, not authenticated.
0accepted 表示四个配置值通过检查,下一行 0 是程序正常结束的退出码。这里使用固定的示例值,接下来检查配置规则:
python -B -X utf8 -m unittest -v test_config末尾应显示 Ran 5 tests 和 OK。测试使用自己的配置字典。看到 OK 后,可以对照下面的字段说明读源码,再到第5节观察无效超时的结果。
核心逻辑分三部分。完整源码见上方代码区:
# 用字段类型描述配置,并让密钥在对象显示时保持隐藏。
from dataclasses import dataclass, field
@dataclass(frozen=True)
class Config:
base_url: str
model: str
timeout_seconds: float
api_key: str = field(repr=False)repr=False 让默认对象表示省略密钥,减少意外打印对象时的泄露。它不是加密:代码依然可以读取 config.api_key,把对象转成完整字典也可能暴露字段。因此仍要控制日志调用点。
第二部分对四个字段逐一检查非空,只在错误里写字段名,不拼接用户输入。第三部分检查等待秒数。比如把超时写成 NaN(“不是一个有效数字”)或 inf(无穷大),程序不能据此决定该等几秒;即使它们是 Python 能表示的特殊数值,也不该作为超时配置。代码用 math.isfinite() 检查“是不是正常的有限数”,再检查允许范围。
这个实验允许的超时区间是 0 到 120 秒,不含 0。这是教学策略,不是供应商对所有请求规定的超时范围。
跟着配置加载顺序排错
load_config(env) 接收一个像字典一样的环境映射。测试传入自建字典,因此不会读取或覆盖你机器上的真实环境。主程序才把 os.environ 传进去。控制流可以拆成五步:
| 顺序 | 检查 | 失败时为什么立即停止 |
|---|---|---|
| 1 | 四个变量都存在且去空白后非空 | 缺配置不应继续猜默认供应商 |
| 2 | Base URL 精确命中允许列表 | 防止密钥被发到任意端点 |
| 3 | 超时字符串能转成浮点数 | 环境变量不会自动变成数字 |
| 4 | 超时有限且位于教学范围 | 拒绝 NaN、无穷和异常等待 |
| 5 | 构造不可变 Config | 把已校验值交给后续请求层 |
@dataclass(frozen=True) 让字段不能被普通赋值随手改掉;它不提供秘密存储。field(repr=False) 只影响默认打印形式,也不意味着序列化、调试器和内存里看不到密钥。安全来自边界、权限与日志纪律,不来自一个装饰器参数。
端点为什么采用精确允许列表
检查器只允许两个明确地址:https://api.deepseek.com 和 https://api.deepseek.com/v1。它不会接受“包含 deepseek 字样”的任意 URL。例如 https://api.deepseek.com.evil.invalid 的主机并不是官方主机。
我们也不接受带用户名密码、查询参数或随意路径的地址。这里直接用精确字符串匹配,是为了使小型入门实验的信任范围清楚。它不是通用 URL 归一化库:结尾斜杠等其他写法也会被拒绝。
管理员接入其他供应商时,应修改受控配置和对应测试。不能让浏览器传入任意端点,再把服务端密钥发送过去。初始地址检查之外,还要限制 HTTP 客户端的重定向、代理与出站网络权限。
5. 配置检查分别检查什么
| 用例 | 预期 | 证明范围 |
|---|---|---|
| 四个字段符合规则 | 产生 Config 对象 | 本地规则可以接受教学配置 |
| 字段为空白 | ValueError | 不带着明显缺失项进入请求阶段 |
| HTTP、伪装域名、额外路径 | 拒绝 | 端点精确允许列表生效 |
| 0、负数、NaN、inf、非数字 | 拒绝 | 超时不能是这些非法输入 |
| 密钥出现在错误测试输入里 | 错误消息不回显该值 | 本例错误路径没有拼接输入值 |
五项测试包含多组输入;打开 test_config.py,可以对照每一种输入会在哪一步被拒绝。
把等待秒数改成 NaN,观察配置检查怎样处理无效数字:
$env:MODEL_TIMEOUT_SECONDS = "NaN"
python -B -X utf8 config.py
$LASTEXITCODEmacOS 终端命令
export MODEL_TIMEOUT_SECONDS="NaN"
python -B -X utf8 config.py
echo $?输出如下,2 表示这次输入被配置检查拒绝:
Configuration rejected: MODEL_TIMEOUT_SECONDS must be within (0, 120]
2恢复为15秒,再运行同一个程序:
$env:MODEL_TIMEOUT_SECONDS = "15"
python -B -X utf8 config.py
$LASTEXITCODEmacOS 终端命令
export MODEL_TIMEOUT_SECONDS="15"
python -B -X utf8 config.py
echo $?结果重新变为 Configuration accepted...,退出码为 0。只改一个配置值,就能看清“读取字符串→转换数字→检查范围”这条处理过程。
6. 从配置检查接到模型请求
配置检查完成后,才进入会产生费用和数据外发的部分。不要把“读取配置”函数悄悄改成“读取后立即发请求”,否则测试与启动探针可能意外消费额度。
建议把真实调用明确拆成以下顺序:
- 操作者核对供应商、当前模型标识、可用权限与价格。
- 后端确认本次用户有权调用,输入允许外发,且额度足够。
- 预留预算,再构造最小请求;首次使用无敏感信息的短问题。
- 记录请求 ID、耗时、状态和供应商返回的用量,校验响应结构。
- 对账实际费用,处理失败与未知状态,最后才把结果交给业务。
接入真实服务时,按 DeepSeek 官方请求示例核对客户端版本、请求参数和返回字段;“兼容接口”不保证所有行为相同。
用账本执行100元预算
MONTHLY_BUDGET=100 本身不会限制任何费用。必须有执行它的账本与拒绝逻辑。多个并发请求都看到“还剩 1 元”,然后各自消费 1 元,就可能超支。
稳妥的做法是先按保守估计原子预留,再根据实际用量对账;无法确认费用的请求不能直接释放全部预留。还要考虑重试、模型切换后的单价、输入与输出计费差异,以及供应商账单与本地计数的差异。没有可靠价格配置时,宁可停止付费调用,也不要猜一个零价格。
预算的预留和结算会在 Token 成本篇用账本实现。
7. 出错时按发生阶段排查
| 阶段与现象 | 先核对 | 下一步 |
|---|---|---|
| 程序启动即报配置缺失 | 当前进程环境、变量名、启动时间 | 修正配置后重新启动 |
| 域名解析或连接失败 | 网络、代理、主机名 | 不打印密钥,保留异常类别 |
| 鉴权或权限被拒绝 | 凭据是否有效、对应账户与权限 | 更正凭据,不做无限重试 |
| 请求参数或模型被拒绝 | 当前 API 文档、路径和模型标识 | 修改请求,而不是重复同一个错误 |
| 限流或服务暂时不可用 | 供应商响应、并发、重试策略 | 有上限退避,并计入预算 |
| 本地超时 | 请求是否已发送、是否收到部分响应 | 记录结果未知,不假定远端免费取消 |
HTTP 状态码需要结合供应商错误体和当前文档解释。不要仅凭一个通用表给所有供应商套相同恢复策略。
密钥泄露时,优先在供应商侧撤销或轮换,检查异常调用,再处理代码、日志和仓库历史。仅删掉当前文件里的字符串不能让已经泄露的密钥失效。不要把真实密钥粘贴到提问区让别人“帮忙检查”。
8. 切换模型时对照接口
环境变量负责选择配置,能力表负责说明接口差异。切换时对照下面几项:
| 项目 | 必须确认的问题 |
|---|---|
| 接口形式 | 使用哪种路径与请求结构 |
| 结构化输出 | 支持哪些约束,不支持时如何失败 |
| 工具调用 | 参数、返回格式、并行与流式行为是否一致 |
| 上下文与输出 | 限制如何计算,超限如何处理 |
| 成本与延迟 | 当前价格和测量条件是什么 |
| 数据边界 | 哪些信息允许发送给这个供应商 |
迁移时先跑固定回归样例,包括错误样例。先观察固定任务的字段、内容、费用和延迟,再逐步切换业务。更换端点、凭据、模型和价格应有同一个配置版本,避免模型已经切换,账本还按旧价格估算。
Java 可从 System.getenv 读取,服务端 TypeScript 可从 process.env 读取,再把得到的字符串交给配置校验器。服务端配置模块应留在服务端,避免随着浏览器组件打包出去。
9. 改一个配置,观察检查结果
课后练习展开习题收起习题
正在读取本地记录…
不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。
选择题 · API密钥放在哪里?
浏览器直接发起请求会暴露供应商密钥。网站应该怎样接入模型?
对应知识点:L0-api
简答题 · 配置通过为什么仍不能调用?
检查器使用假密钥和lesson-model仍然输出accepted。请解释它验证了什么、没有验证什么,以及为什么不能把它当成供应商联调成功。
对应知识点:L0-api
代码题 · 不让错误消息泄露输入
扩展config.py测试:用伪装域名、NaN超时和空密钥验证拒绝;把一个假秘密写进非法超时字段,断言错误文本不含它。解释为什么repr=False不是加密。
对应知识点:L0-api
场景题 · 超时以后可以释放预算吗?
请求已发往供应商,但客户端超时没有收到usage。系统想释放全部预留费用并立即重试;同时准备切换模型。指出风险并设计状态与核对步骤。
对应知识点:L0-api
在设置过示例变量的同一个 PowerShell 窗口逐行执行:
$env:MODEL_TIMEOUT_SECONDS = "NaN"
python -X utf8 config.py
$env:MODEL_TIMEOUT_SECONDS = "15"
Remove-Item Env:MODEL_API_KEY
python -X utf8 config.py
$env:MODEL_API_KEY = "test-secret-not-valid"
python -X utf8 config.pymacOS 终端命令
export MODEL_TIMEOUT_SECONDS="NaN"
python -X utf8 config.py
export MODEL_TIMEOUT_SECONDS="15"
unset MODEL_API_KEY
python -X utf8 config.py
export MODEL_API_KEY="test-secret-not-valid"
python -X utf8 config.py第一次是超时值错误,第二次是密钥缺失,最后恢复为接受。Remove-Item Env:MODEL_API_KEY 删除当前终端的这个示例变量;它不会删除电脑上的文件。
回顾
先用假密钥跑通配置检查,再删除密钥或修改端点,观察错误指出哪个字段。配置检查回答的是“参数能否交给客户端”;实际响应要在发送请求后继续检查。
端点告诉客户端向哪里发送,模型名选择目标模型,超时限制等待,密钥用于鉴权。服务端读入这些值后先校验,再交给客户端使用。
这里假密钥实验中,允许的端点通过检查,NaN 超时以退出码 2 被拒绝;这一步只验证配置,不验证真实账户鉴权。请把正常与错误退出码、去掉秘密的日志和你画的密钥边界一起保存。真实接入前可查 DeepSeek 官方文档 与 Python 环境变量。
下一篇:任务建模、自治边界与成功标准。有了可配置的调用入口,还需要决定哪些任务值得交给模型,以及怎样证明任务真的完成。
参考资料
- DeepSeek API 官方文档:真实接入前核对当前端点、模型标识、请求和错误格式。
- Python
os.environ:进程环境变量的读取接口。 - 连接、读取超时、重试和重定向的行为,以所选 SDK 版本的文档为准。
描述会发送给本站维护者,用于修订文章,保存90天。请只填写文章问题,省略个人信息、密钥和内部资料。