本文来自《AI 应用开发课程》月份 1 课程文档,已整理为网站文章版本。
学习目标
学完本节后,你应当能够:
- 使用
.env管理本地敏感配置。 - 使用
python-dotenv加载环境变量。 - 建立最小日志输出。
- 使用
ruff做静态检查。
前置知识
- 已初始化
llm_api_lab
1. 环境变量配置
创建 .env.example:
DEEPSEEK_API_KEY=
DEEPSEEK_BASE_URL=https://api.deepseek.com
DEEPSEEK_MODEL=deepseek-chat
复制为 .env:
cp .env.example .env
然后在 .env 中填入真实 Key。
2. 使用 python-dotenv
示例:
import os
from dotenv import load_dotenv
load_dotenv()
api_key = os.getenv("DEEPSEEK_API_KEY")
if not api_key:
raise ValueError("DEEPSEEK_API_KEY 未设置")
要点:
load_dotenv()负责把.env读进当前进程环境变量- 读取后仍然使用
os.getenv
3. 为什么不能把配置散落在代码里
错误示例:
API_KEY = "hard-coded-key"
BASE_URL = "https://api.deepseek.com"
问题:
- 不安全
- 不方便切环境
- 后续切模型、切厂商会变得混乱
4. 最小日志
在月份 1,你不需要复杂日志系统,但需要最小可读日志。
示例:
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
)
logger = logging.getLogger(__name__)
logger.info("application started")
用途:
- 记录模型请求开始与结束
- 记录工具调用
- 记录错误
5. Ruff 最小使用方式
执行:
uv run ruff check .
作用:
- 发现未使用导入
- 发现明显代码风格问题
- 发现一些低级错误
月份 1 的目标不是研究所有规则,而是养成“先检查再提交”的习惯。
6. 推荐的配置组织
建议在 app/config.py 中收口配置逻辑,在 app/logger.py 中收口日志逻辑。
好处:
- 入口文件更干净
- 后续 CLI 和 FastAPI 都能复用
7. 实操任务
- 写
.env.example - 在
config.py中读取 DeepSeek 相关配置 - 在
logger.py中创建最小日志器 - 故意引入一个未使用变量,运行
ruff check观察输出
8. 自测题
.env.example和.env的区别是什么?- 为什么日志不能只靠
print? - 为什么配置加载逻辑应该集中管理?
9. 作业与验收
验收标准:
.env.example已存在.env已能被成功加载- 项目运行时能输出最小日志
ruff check .没有基础错误
10. 常见错误
- 把真实
.env提交到仓库 - 代码里混用
print和日志但没有边界 - 报错后只看终端最后一行,不看上下文日志
11. 本章与前文关系
上一章解决的是“项目怎么初始化”;这一章解决的是“项目初始化后,最先应该放进哪些工程基础设施”。
对月份 1 来说,优先级最高的三件事是:
- 配置收口
- 日志可见
- 静态检查可跑
这三件事一旦缺失,后面所有模型调用、Tool Calling 和 FastAPI 代码都更容易变乱。
12. 本章在研发助手项目中的位置
研发助手项目最终至少会有:
- 一套稳定配置
- 一套最小日志
- 一套最小代码检查流程
这三者看似“基础设施”,但它们会直接影响:
- 你能不能快速排 API Key 问题
- 你能不能定位工具调用失败的阶段
- 你能不能在代码越来越多时维持可读性
13. 为什么 .env、日志和 ruff 要一起学
因为这三者分别对应项目工程中的三条底线:
.env
解决“配置不能散落”的问题。
日志
解决“运行时到底发生了什么”的问题。
ruff
解决“代码还没跑就能发现一批低级问题”的问题。
月份 1 不是要你成为日志系统专家或代码规范专家,而是要建立“每个项目都默认带这些底盘”的习惯。
14. 错误示例 vs 正确示例
错误示例:配置写死 + 全程 print
API_KEY = "hard-coded-key"
BASE_URL = "https://api.deepseek.com"
print("start calling model")
print("call failed")
问题:
- 密钥不安全
- 配置不可替换
- 日志不可分类
- 后续 API 层和 service 层很难共享统一输出
正确示例:配置收口 + 日志最小化规范
import logging
import os
from dotenv import load_dotenv
load_dotenv()
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
)
logger = logging.getLogger(__name__)
api_key = os.getenv("DEEPSEEK_API_KEY")
logger.info("configuration loaded")
15. 完整文件级示例:config.py + logger.py
config.py
"""项目配置读取。"""
from __future__ import annotations
import os
from dataclasses import dataclass
from dotenv import load_dotenv
load_dotenv()
@dataclass
class AppSettings:
"""月份 1 最小配置对象。"""
deepseek_api_key: str
deepseek_base_url: str
deepseek_model: str
def load_settings() -> AppSettings:
"""从环境变量中加载配置。
当前阶段先用数据类承载,后面会自然升级到 Pydantic。
"""
api_key = os.getenv("DEEPSEEK_API_KEY")
if not api_key:
raise ValueError("DEEPSEEK_API_KEY 未设置")
return AppSettings(
deepseek_api_key=api_key,
deepseek_base_url=os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com"),
deepseek_model=os.getenv("DEEPSEEK_MODEL", "deepseek-chat"),
)
logger.py
"""项目日志初始化。"""
from __future__ import annotations
import logging
def get_logger(name: str) -> logging.Logger:
"""返回一个最小可用的 logger。
月份 1 不做复杂 logging 配置,只先保证:
- 有时间
- 有级别
- 有模块名
"""
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
)
return logging.getLogger(name)
16. 逐段解释这组完整示例
为什么 load_dotenv() 放在配置模块里
因为月份 1 希望把“配置读取入口”收口。你后面不应该在每个脚本里都复制一遍 load_dotenv()。
为什么 AppSettings 要存在
因为即使当前只有三个字段,把它们收口成对象,也比到处传裸字符串更稳定。
为什么 logger 不复杂化
初学者常见错误是:一上来就想设计完整日志系统。月份 1 只需要最小可见性,不需要追求过度工程。
17. ruff 在月份 1 的真实价值
很多人把 ruff 理解为“格式工具”或“规范工具”。对月份 1 来说,它更像:
- 语法和导入层面的早期预警器
- 防止代码越来越乱的低成本手段
例如它能帮你发现:
- 未使用导入
- 显而易见的低级问题
- 某些不一致的代码习惯
月份 1 不是研究所有 lint 规则,而是建立一个底线:代码改完先检查。
18. 一个更接近后续项目的增强版用法
你可以在入口文件中这样组合使用:
from app.config import load_settings
from app.logger import get_logger
logger = get_logger(__name__)
def main() -> None:
settings = load_settings()
logger.info("application_started")
logger.info("current_model=%s", settings.deepseek_model)
这其实已经在为后面的 llm_client.py 和 chat_service.py 做铺垫。
19. 调试与排错:本章最常见问题
问题一:.env 改了但程序似乎没读到
先确认:
- 当前工作目录是否正确
.env是否真的存在- 代码是否调用了
load_dotenv()
问题二:日志没输出
检查:
- 是否真的调用了
logger.info(...) - logging level 是否为
INFO
问题三:ruff 报错看不懂
不要一上来全部忽略。月份 1 的策略是:
- 先看它指出的是什么文件、哪一行
- 优先修明显错误和未使用导入
- 不在本阶段陷入复杂规则定制
20. 本章完成后你应该具备的能力
完成本章后,你应当做到:
- 能用
.env管理本地配置。 - 能通过配置对象读取模型设置。
- 能输出最小日志。
- 能运行
ruff做基础检查。
21. 如果你卡在这里,先回看哪几章
- 项目目录还没理顺:回看 01-uv与项目初始化.md
- 不理解函数和模块边界:回看 02-函数-模块-包.md
22. 从本章过渡到下一章的桥接说明
接下来进入 03-pydantic与配置建模.md。
这一章已经让你学会“配置应该被收口”,下一章会进一步升级这个思路:不仅配置要结构化,消息、请求、响应、错误这些对象也要结构化。也就是从“配置工程化”进入“数据工程化”。