Skip to content

02|调用与环境:settings 与 base_url

上一篇说了 agent 靠调用模型实现能力。这一篇先把这第一次调用在本机跑通。

讨论 agent 怎么理解任务之前,得先让调用本身稳定。API Key 怎么管、模型名写在哪、用哪个端点、出错怎么分类——这些不弄清楚,后面的设计都悬空。本篇建立全系列的配置约定:所有代码都从一个 settings 对象取配置,不散落在几十个文件里硬编码。

配置集中:为什么要收进 settings

最直接的写法是在调用处写死:

AIOps 概念图:settings 集中配置

python
client = OpenAI(api_key="sk-xxx")
response = client.responses.create(model="gpt-5.5", input="...")

能跑,但后面每篇都要调模型,散落在几十个文件里的 Key 和模型名,一旦要换就是一场搜索游戏。模型升级了要改几十处,Key 轮换了要改几十处,测试环境和生产环境用不同端点也要改几十处。

把配置收进一个模块,全系列统一从 settings 取值,改一处全生效。这是第一个设计决策,也是后面所有篇的基础。

最小配置:Key、model、base_url

config.py

python
import os
from dataclasses import dataclass, field


@dataclass
class Settings:
    api_key: str = field(default_factory=lambda: os.environ.get("OPENAI_API_KEY", ""))
    # 示例默认值,以账号实际可用模型为准
    model: str = field(default_factory=lambda: os.environ.get("OPENAI_MODEL", "gpt-5.5"))
    # base_url 留空走官方端点;填地址走第三方兼容端点
    base_url: str = field(default_factory=lambda: os.environ.get("OPENAI_BASE_URL", ""))
    timeout: float = 60.0


def load_settings() -> Settings:
    return Settings()

default_factory 让取值发生在实例化时,而不是模块导入时。这点很重要:load_dotenv() 先把 .env 读进环境变量,再 load_settings() 才能拿到值。顺序反了,settings.api_key 会是空串。

.env

text
OPENAI_API_KEY=sk-替换为真实密钥
OPENAI_MODEL=gpt-5.5
# 用官方端点时这行留空或不写
# OPENAI_BASE_URL=https://api.oneapi.example/v1

.env.example 只写变量名不填值,.env 不提交到版本库。

base_url:为什么要单独支持

base_url 是这套配置里最容易被教程忽略、但国内开发者几乎必用的一个字段。

OpenAI 官方端点在国内访问不稳定,很多人走第三方兼容端点:one-api、new-api 这类聚合代理,或者 DeepSeek、Kimi、通义千问这些国产模型的 OpenAI 兼容接口。这些端点都兼容 OpenAI 协议,但地址不同。base_url 就是告诉 SDK 把请求发到哪个地址。

python
from openai import OpenAI
from config import load_settings
from dotenv import load_dotenv

load_dotenv()
settings = load_settings()

client = OpenAI(
    api_key=settings.api_key,
    base_url=settings.base_url or None,   # 留空传 None,走官方端点
    timeout=settings.timeout,
)

base_url or None 这个写法是为了兼容留空:空字符串传给 SDK 会报错,传 None 走官方默认端点。这样官方端点和第三方端点用同一份代码,只改 .env

用 base_url 时的设计注意点

走第三方兼容端点不是改个地址就完事,有几个坑要在设计阶段就知道:

模型名要对上端点。官方端点认 gpt-5.5,但 one-api 这类代理可能用 gpt-4odeepseek-chat 之类的名字,取决于后端接的是哪家。settings.model 的值必须和 base_url 指向的端点能对上,否则报 model not found。这就是为什么模型名要从配置读——换端点往往要同时换模型名。

不是所有特性都支持。第三方端点兼容的是 OpenAI 协议的主体,但一些较新的特性(某些 reasoning 参数、特定的工具调用格式)未必支持。生产前要测,不能假设官方支持的第三方都支持。

国产模型的兼容接口。DeepSeek、Kimi、通义千问都提供 OpenAI 兼容接口,base_url 指过去、模型名换成它家的就能跑。但它们底层不是 OpenAI 模型,行为会有差异——比如工具调用的稳定性、对 strict schema 的支持程度。本系列代码以 OpenAI 协议为主线,遇到这些差异在实际接入时测一下。

最小调用:把告警发给模型

配置就绪,写第一个调用脚本。scripts/check_alert.py

python
from pathlib import Path
from openai import OpenAI
from config import load_settings
from dotenv import load_dotenv

load_dotenv()
settings = load_settings()

client = OpenAI(
    api_key=settings.api_key,
    base_url=settings.base_url or None,
    timeout=settings.timeout,
)

alert = Path("data/alerts/nginx-502.txt").read_text(encoding="utf-8")

response = client.responses.create(
    model=settings.model,
    instructions="从告警中提取现象、涉及对象和第一条建议检查项。输出三行。",
    input=alert,
    max_output_tokens=200,
)

print(response.output_text)

告警文本从文件读,不写死在代码里。这里要消掉一个常见误解:模型没有读取 data/alerts/ 目录的能力。读文件的是 Python,模型只看到 alert 变量里的文本。如果模型回答"找不到文件",问题不在模型,在脚本没把内容传进 input

出错时:先分清是哪一层

调用跑不通时,错误来自三个不同层,排查方向完全不同。看是哪一类,就知道往哪查。

AIOps 概念图:调用错误分层

没放 Key 时报的是 Python 自身的 KeyError——环境变量没读到,请求根本没发出去。查 load_dotenv() 顺序、变量名拼写、.env 是否存在。

放进 Key 后再出错,错误来自 OpenAI SDK 的异常类。SDK 把服务端返回的不同错误对应成不同异常:

异常类HTTP含义排查方向
AuthenticationError401请求到了平台,Key 无效Key 是否轮换、是否误用组织 Key
NotFoundError404账号或端点没有这个模型模型名是否和 base_url 端点对上、账号权限
BadRequestError400请求体有问题上下文超长、参数非法、媒体过大
RateLimitError429触发限流退避重试,或申请提额
APITimeoutError请求发出但超时网络、代理、模型负载
APIConnectionError请求没出本机代理、DNS、防火墙

走第三方端点时,NotFoundError 特别常见——模型名和端点对不上就报这个。APIConnectionError 说明请求没出本机,检查代理和 DNS。AuthenticationError 说明网络是通的,换网络没用。先分清错误在哪一层,比盲目重试重要得多。

推理控制:effort 与 verbosity

配置里还有两个参数影响成本和延迟,虽然在调用时传,但设计阶段就该知道。

推理模型(如 gpt-5.5)的延迟和成本,很大一部分花在"内部推理"上,不是可见输出。两个参数控制这部分开销:

python
response = client.responses.create(
    model=settings.model,
    instructions="从告警中提取现象、涉及对象和第一条建议检查项。输出三行。",
    input=alert,
    reasoning={"effort": "medium"},   # low | medium | high
    text={"verbosity": "medium"},     # low | medium | high
    max_output_tokens=200,
)

reasoning.effort 决定模型花多少内部推理 token。告警摘要这类轻任务用 low 能把响应压到几秒,根因分析这类需要多步推断的任务才上 hightext.verbosity 决定可见输出的详尽程度,和推理深度是两件事——可以深思考但只给结论。

不传都默认 mediumeffort 越高推理 token 越多,账单和延迟同步上升,这是后面成本优化的主要杠杆。非推理模型传这两个参数会被忽略。

落到 settings 的完整配置

把这一篇建立的配置约定固化下来,后面所有篇都从这里取值:

python
@dataclass
class Settings:
    api_key: str = field(default_factory=lambda: os.environ.get("OPENAI_API_KEY", ""))
    model: str = field(default_factory=lambda: os.environ.get("OPENAI_MODEL", "gpt-5.5"))
    base_url: str = field(default_factory=lambda: os.environ.get("OPENAI_BASE_URL", ""))
    timeout: float = 60.0
    reasoning_effort: str = "medium"
    verbosity: str = "medium"

超时设 60 秒是告警排查场景的合理默认——模型响应通常 5~15 秒,凌晨网络抖动可能到 30 秒以上。设 5 秒会制造大量误失败,设 300 秒让异常请求长期挂起。按 P99 延迟的 2~3 倍设。

.env 只是避免把密钥写进代码仓库,不是安全策略。生产环境用密钥管理服务(KMS / Vault / 云厂商 Secret Manager),本地开发用 .env,CI/CD 用环境变量注入。不要让 .env 出现在容器镜像里。

调用跑通了,下一篇解决"任务怎么说清楚"。模型能稳定回答,不代表它理解了任务——很多时候模型跑偏,不是能力不行,是任务没说清。