AgentDepthAgent 工程学习指南
输入概念或问题,检索正文和待编写目录。
第一次学习?从 L0 总览开始 →

模型 API、密钥与环境变量:第一次请求之前要检查什么

我的学习 →

正在读取学习记录…

模型 API 是程序向模型服务发送请求并取得响应的接口。一次调用至少要知道服务地址、模型名以及如何证明调用者身份;密钥就是这类身份凭据之一。配置检查器先读取这些值,确认程序准备发往哪里,再把通过检查的配置交给请求代码。

这里先用教学用假密钥跑通一次本地配置检查,不需要真实模型账号。看懂正常配置从哪里来、流向哪里之后,再故意留空模型名或写错地址,观察错误怎样在发出请求前被发现。

需要回查请求和等待机制时,可以打开 HTTP、REST 与 JSON和异步与超时。

0. 一次请求的配置流向

Agent 的推理循环可能连续调用模型多次,还可能把工具结果和企业资料发送给供应商。一个普通脚本里的错误端点、泄露密钥或无上限重试,会被循环放大成数据和费用事故。因此第一次请求之前,要先把“配置是否合法”和“真实请求是否获准”拆成两个明确阶段。

浏览器请求进入带盾牌和密钥图标的网站后端,完成身份额度输入和端点检查后,再访问允许的模型API
图1:供应商密钥停留在服务端。配置检查通过后仍要经过用户权限、数据边界和预算判断。

1. 一次模型调用究竟依赖什么

至少有四个相互独立的配置:请求发到哪里、由谁付费、调用哪个模型、等多久。把它们混成一个“模型配置字符串”,很容易在切换环境时弄错。

配置作用常见误解
Base URLAPI 服务的基础地址地址包含品牌名就一定可信
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:

Python
"""读取并检查模型连接配置。"""
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
Python
# 核对模型 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:

Bash
pwd
ls
python --version

Get-ChildItem 应看到 config.py 与 test_config.py。下面的 $env:名称 = "值" 只为当前 PowerShell 进程及它随后启动的 Python 子进程设置变量;关闭窗口后通常消失,也不会自动更新已经启动的服务。

PowerShell
$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
$LASTEXITCODE
macOS 终端命令
Bash
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 $?

配置检查预期输出:

TXT
Configuration accepted; no request sent; key presence checked, not authenticated.
0

accepted 表示四个配置值通过检查,下一行 0 是程序正常结束的退出码。这里使用固定的示例值,接下来检查配置规则:

Bash
python -B -X utf8 -m unittest -v test_config

末尾应显示 Ran 5 tests 和 OK。测试使用自己的配置字典。看到 OK 后,可以对照下面的字段说明读源码,再到第5节观察无效超时的结果。

核心逻辑分三部分。完整源码见上方代码区:

Python
# 用字段类型描述配置,并让密钥在对象显示时保持隐藏。
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四个变量都存在且去空白后非空缺配置不应继续猜默认供应商
2Base 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,观察配置检查怎样处理无效数字:

PowerShell
$env:MODEL_TIMEOUT_SECONDS = "NaN"
python -B -X utf8 config.py
$LASTEXITCODE
macOS 终端命令
Bash
export MODEL_TIMEOUT_SECONDS="NaN"
python -B -X utf8 config.py
echo $?

输出如下,2 表示这次输入被配置检查拒绝:

TXT
Configuration rejected: MODEL_TIMEOUT_SECONDS must be within (0, 120]
2

恢复为15秒,再运行同一个程序:

PowerShell
$env:MODEL_TIMEOUT_SECONDS = "15"
python -B -X utf8 config.py
$LASTEXITCODE
macOS 终端命令
Bash
export MODEL_TIMEOUT_SECONDS="15"
python -B -X utf8 config.py
echo $?

结果重新变为 Configuration accepted...,退出码为 0。只改一个配置值,就能看清“读取字符串→转换数字→检查范围”这条处理过程。

6. 从配置检查接到模型请求

配置检查完成后,才进入会产生费用和数据外发的部分。不要把“读取配置”函数悄悄改成“读取后立即发请求”,否则测试与启动探针可能意外消费额度。

建议把真实调用明确拆成以下顺序:

  1. 操作者核对供应商、当前模型标识、可用权限与价格。
  2. 后端确认本次用户有权调用,输入允许外发,且额度足够。
  3. 预留预算,再构造最小请求;首次使用无敏感信息的短问题。
  4. 记录请求 ID、耗时、状态和供应商返回的用量,校验响应结构。
  5. 对账实际费用,处理失败与未知状态,最后才把结果交给业务。
模型 API 调用的四步检查:配置、连接与鉴权、用户与数据权限、预算与对账
图3:调用前准备配置、确认连接和权限、预留预算;调用后记录用量并核对费用。

接入真实服务时,按 DeepSeek 官方请求示例核对客户端版本、请求参数和返回字段;“兼容接口”不保证所有行为相同。

用账本执行100元预算

MONTHLY_BUDGET=100 本身不会限制任何费用。必须有执行它的账本与拒绝逻辑。多个并发请求都看到“还剩 1 元”,然后各自消费 1 元,就可能超支。

稳妥的做法是先按保守估计原子预留,再根据实际用量对账;无法确认费用的请求不能直接释放全部预留。还要考虑重试、模型切换后的单价、输入与输出计费差异,以及供应商账单与本地计数的差异。没有可靠价格配置时,宁可停止付费调用,也不要猜一个零价格。

预算的预留和结算会在 Token 成本篇用账本实现。

7. 出错时按发生阶段排查

阶段与现象先核对下一步
程序启动即报配置缺失当前进程环境、变量名、启动时间修正配置后重新启动
域名解析或连接失败网络、代理、主机名不打印密钥,保留异常类别
鉴权或权限被拒绝凭据是否有效、对应账户与权限更正凭据,不做无限重试
请求参数或模型被拒绝当前 API 文档、路径和模型标识修改请求,而不是重复同一个错误
限流或服务暂时不可用供应商响应、并发、重试策略有上限退避,并计入预算
本地超时请求是否已发送、是否收到部分响应记录结果未知,不假定远端免费取消

HTTP 状态码需要结合供应商错误体和当前文档解释。不要仅凭一个通用表给所有供应商套相同恢复策略。

密钥泄露时,优先在供应商侧撤销或轮换,检查异常调用,再处理代码、日志和仓库历史。仅删掉当前文件里的字符串不能让已经泄露的密钥失效。不要把真实密钥粘贴到提问区让别人“帮忙检查”。

8. 切换模型时对照接口

环境变量负责选择配置,能力表负责说明接口差异。切换时对照下面几项:

项目必须确认的问题
接口形式使用哪种路径与请求结构
结构化输出支持哪些约束,不支持时如何失败
工具调用参数、返回格式、并行与流式行为是否一致
上下文与输出限制如何计算,超限如何处理
成本与延迟当前价格和测量条件是什么
数据边界哪些信息允许发送给这个供应商

迁移时先跑固定回归样例,包括错误样例。先观察固定任务的字段、内容、费用和延迟,再逐步切换业务。更换端点、凭据、模型和价格应有同一个配置版本,避免模型已经切换,账本还按旧价格估算。

Java 可从 System.getenv 读取,服务端 TypeScript 可从 process.env 读取,再把得到的字符串交给配置校验器。服务端配置模块应留在服务端,避免随着浏览器组件打包出去。

9. 改一个配置,观察检查结果

课后练习展开习题收起习题

4 道练习 · 客观题规则评分 · 其余题目待批改

正在读取本地记录…

不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。

练习 1 / 4 · v2.0.0

选择题 · API密钥放在哪里?

浏览器直接发起请求会暴露供应商密钥。网站应该怎样接入模型?

对应知识点:L0-api

选择一个答案
练习 2 / 4 · v2.0.0

简答题 · 配置通过为什么仍不能调用?

检查器使用假密钥和lesson-model仍然输出accepted。请解释它验证了什么、没有验证什么,以及为什么不能把它当成供应商联调成功。

对应知识点:L0-api

练习 3 / 4 · v2.0.0

代码题 · 不让错误消息泄露输入

扩展config.py测试:用伪装域名、NaN超时和空密钥验证拒绝;把一个假秘密写进非法超时字段,断言错误文本不含它。解释为什么repr=False不是加密。

对应知识点:L0-api

练习 4 / 4 · v2.0.0

场景题 · 超时以后可以释放预算吗?

请求已发往供应商,但客户端超时没有收到usage。系统想释放全部预留费用并立即重试;同时准备切换模型。指出风险并设计状态与核对步骤。

对应知识点:L0-api

在设置过示例变量的同一个 PowerShell 窗口逐行执行:

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.py
macOS 终端命令
Bash
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 版本的文档为准。
图片查看器
100%

文章配图 · 按 Esc 关闭