agent学习Day11——多环境配置与完整JD分析接口
一、多环境配置让配置跟环境走三件事一是 OS 环境变量优先级最高——export KEYxxx盖过.env文件生产敏感信息只放 OS 不落盘。二是 env_file 分层——.env通用默认可进 git.env.development/.env.production环境覆盖gitignore.env.local个人本地。三是 ENV 字段标识——告诉代码我在哪个环境据此选 env_file。pydantic-settings 的一个坑model_config SettingsConfigDict(env_file...)是类定义时求值的那时self.ENV字段还没读到没法写env_filef.env.{self.ENV}。解法类定义前先从 OS 环境变量读 ENV 到模块级变量importosfrompydantic_settingsimportBaseSettings,SettingsConfigDict _env_nameos.environ.get(ENV,development)classSettings(BaseSettings):model_configSettingsConfigDict(env_file(.env,f.env.{_env_name}),# 元组后者覆盖前者env_file_encodingutf-8,extraignore,)ENV:strdevelopmentLOG_LEVEL:strDEBUGLOG_FILE:strlogs/app.log# ... 其他字段env_file接收元组的语义pydantic-settings 按顺序加载后面的文件覆盖前面的——同名 key 后者生效。所以.env是底座默认值.env.{env_name}是环境特定覆盖。配置优先级全景实例化参数 OS 环境变量 .env 文件 字段默认值生产环境你在 OS 设DEEPSEEK_API_KEY正式key本地用.env写DEEPSEEK_API_KEY测试key——同一份代码、不同环境自动读到不同值不用改代码。这就是多环境配置能跑起来的底层机制。二、结构化日志从 print 到 dictConfigprint 的五个毛病没级别INFO/WARN/ERROR 混在一起没法过滤没时间戳、没文件名、没行号不能同时输出到终端和文件不能按级别动态切换dev 想看 DEBUGprod 只看 INFO关掉print要改代码logging模块解决以上所有问题。dictConfig是结构化配置——把日志怎么打用字典声明比代码一行行logger.addHandler(...)配置更清晰、更易切换。三件套Logger / Handler / FormatterLogger入口你写logger.info(xxx)就是给它发消息。它决定这条消息要不要往下传按级别过滤。Handler出口决定消息发到哪儿——终端StreamHandler、文件FileHandler、网络、邮件。每个 Handler 各自管自己的级别过滤。Formatter格式化决定消息长啥样——加时间戳、文件名、行号、级别标签。两层过滤最容易绕晕的地方Logger 设级别 “低于这个级别的消息我直接丢不传给 Handler”Handler 设级别 “低于这个级别的消息我收到了但也不输出”两者取交集——消息必须通过 Logger 级别 Handler 级别两层才能输出我自己的需求是终端 DEBUG 文件 INFO靠的就是两层过滤Logger 级别设 DEBUG让 DEBUG 消息能传出去两个 Handler 各自设级别终端 DEBUG 全收、文件 INFO 起步。举例Logger 级别DEBUGHandler 级别INFO。logger.debug(x)通过 LoggerDEBUG≥DEBUG但被 Handler 拦下DEBUGINFO→不输出logger.info(x)两层都通过 →输出dictConfig 落地importlogging.configfrompathlibimportPathfromapp.core.configimportSettingsdefsetup_logging(settings:Settings)-None:# 提前建日志目录否则 FileHandler 第一次写文件时崩log_pathPath(settings.LOG_FILE)log_path.parent.mkdir(parentsTrue,exist_okTrue)logging.config.dictConfig({version:1,disable_existing_loggers:False,# 关键不禁用已注册 loggeruvicorn 等formatters:{default:{format:%(asctime)s | %(levelname)-8s | %(name)s | %(message)s,datefmt:%Y-%m-%d %H:%M:%S,},},handlers:{console:{class:logging.StreamHandler,level:DEBUG,formatter:default,stream:ext://sys.stdout,# ext:// 协议引用 Python 对象},file:{class:logging.FileHandler,level:INFO,formatter:default,filename:settings.LOG_FILE,encoding:utf-8,},},loggers:{app:{level:settings.LOG_LEVEL.upper(),# 动态读配置不写死handlers:[console,file],propagate:False,# 切断向父 logger 传播避免重复输出},},root:{level:WARNING,handlers:[console],},})几个关键点我特意记一下disable_existing_loggers: False—— 默认True会禁用所有已存在 logger包括 uvicorn 的导致 uvicorn 访问日志消失。一律写False。propagate: False—— 子 logger 处理完消息后默认会向父 logger 传播父 logger 再处理一遍 → 重复输出。切断传播避免重复。ext://sys.stdout—— dictConfig 是纯字典配置不能直接写 Python 对象用ext://协议字符串引用。ext://sys.stdoutsys.stdout。settings.LOG_LEVEL.upper()—— 动态读配置不写死 DEBUG。生产 OS 设LOG_LEVELINFO自动屏蔽 DEBUG排查时改LOG_LEVELDEBUG立刻打开不用动代码。logger 继承logging.getLogger(app.main)创建的 logger 名为app.mainPython logging 把.当层级分隔符——app.main是app的子 logger。子 logger 没设级别 → 继承父 logger 的级别没自己的 handler → 消息 propagate 到父 logger由父的 handler 处理。app设了propagateFalse→ 不再向 root 传播链条到此为止。三、完整接口整合把散装能力串成链路这一节是把前面几篇学的依赖注入、LlmService、async、异常处理、重试、mock 测试、日志全整合到一个接口里。前面那些知识点都是为这一刻铺路。三层架构这节的灵魂层文件职责厚薄路由层app/api/routes/jd.py接收请求、调服务、返回薄不写业务逻辑、不 try/except服务层app/services/llm_service.py业务逻辑、调 LLM、解析、异常映射厚依赖层app/api/dependencies.py注入服务实例薄反模式路由里直接OpenAI()调 LLM——三层混一起无法测试、无法替换。路由核心链路router.post(/analyze,response_modelJdAnalysisResult)asyncdefanalyze(req:JdAnalyzeRequest,llm_service:LlmServiceDepends(get_llm_service),):logger.info(analyze called, jd_text length%s,len(req.jd_text))resultawaitllm_service.analyze_jd(req.jd_text)logger.info(analyze success: job_title%s, skills_count%s,result.job_title,len(result.required_skills))returnresultresponse_modelJdAnalysisResult—— FastAPI 自动序列化 生成 /docs 字段过滤req: JdAnalyzeRequest—— FastAPI 自动用 Pydantic 校验请求体失败抛RequestValidationError→ 422Depends(get_llm_service)—— 依赖注入路由不 new 服务解耦路由不写 try/except—— 异常由全局处理器接异常映射全景场景异常状态码语义请求 jd_text 缺失/过短/过长RequestValidationError422客户端错LLM 重试耗尽ServiceError503上游不可用LLM 返回坏 JSONBadGatewayError502上游返回无效响应其他未捕获Exception兜底500服务端 bug坏 JSON 的 502 映射是这节的核心。之前parse_jd_analysis抛ValueError或 pydanticValidationError走 Exception 兜底 → 500语义错位——把上游的锅算成了我们的 bug。HTTP 状态码语义500 Internal Server Error—— “服务端自己出 bug”。是我们的错不该重试得修代码。502 Bad Gateway—— “上游网关返回了无效响应”。上游返回了垃圾不是我们的 bug重试可能成功。503 Service Unavailable—— “服务暂时不可用”。上游连不上/超时/限流重试可能成功。坏 JSON 是 LLM上游返回了垃圾——语义上该是 502不是 500。修复在analyze_jd内 try/except 捕获ValueErrorPydanticValidationError抛BadGatewayError(502)。frompydanticimportValidationErrorasPydanticValidationErrorfromapp.core.exceptionsimportBadGatewayErrorasyncdefanalyze_jd(self,jd_text:str)-JdAnalysisResult:rawawaitself.call_llm(...)try:returnparse_jd_analysis(raw)except(ValueError,PydanticValidationError)ase:raiseBadGatewayError(messageLLM 返回的内容无法解析为有效 JSON,detailstr(e),)起别名PydanticValidationError避免跟项目的ValidationError422 那个撞名——这是这节最容易踩的坑。两个ValidationError完全不同名字来源用途是不是 AppExceptionapp.core.exceptions.ValidationError我们项目AppException 子类code422路由层抛✅ 是pydantic.ValidationErrorPydantic 库model_validate失败时自动抛❌ 不是4 场景端到端测试用 TestClient dependency_overrides注入假 service测 HTTP 链路 异常映射classFakeLlmService:鸭子类型假 service不继承 LlmService 避免触发 AsyncOpenAI 初始化。def__init__(self,return_valueNone,raise_exceptionNone):self.return_valuereturn_value self.raise_exceptionraise_exceptionasyncdefanalyze_jd(self,jd_text:str):ifself.raise_exception:raiseself.raise_exceptionreturnself.return_valuepytest.fixture(autouseTrue)defcleanup_overrides():yieldapp.dependency_overrides.clear()# 避免测试间污染deftest_analyze_success(client):app.dependency_overrides[get_llm_service]lambda:FakeLlmService(return_value_make_result())responseclient.post(/api/v1/jd/analyze,json{jd_text:VALID_JD})assertresponse.status_code200deftest_analyze_short_jd_text_422(client):responseclient.post(/api/v1/jd/analyze,json{jd_text:短})assertresponse.status_code422# FastAPI 自动校验FakeLlmService 没被调deftest_analyze_llm_unavailable_503(client):app.dependency_overrides[get_llm_service]lambda:FakeLlmService(raise_exceptionServiceError(messageLLM 超时))responseclient.post(/api/v1/jd/analyze,json{jd_text:VALID_JD})assertresponse.status_code503deftest_analyze_bad_json_502(client):app.dependency_overrides[get_llm_service]lambda:FakeLlmService(raise_exceptionBadGatewayError(message无法解析))responseclient.post(/api/v1/jd/analyze,json{jd_text:VALID_JD})assertresponse.status_code502两种 mock 模式的区别这个区分是测试设计的灵魂服务层单元测试test_analyze_jd_mock.pymockclient.chat.completions.create网络边界业务逻辑真跑——验加工过程对不对端到端集成测试test_jd_analyze.pymockLlmService整个鸭子类型 FakeLlmService测 HTTP 链路 异常映射——验调度对不对比喻服务层单元测试是验工厂流水线对不对端到端测试是验整条供应链调度对不对。两者互补不能互相替代。四、几个我踩过的坑把变量名写成了字符串字面量改logging_config.py时我把settings.LOG_LEVEL.upper()调用对象属性方法写成了settings.LOG_LEVEL.upper()字符串字面量.upper()。dictConfig 收到SETTINGS.LOG_LEVEL找不到对应级别抛 ValueErrorpytest 加载 conftest 时炸。教训Python 里xxx是字符串字面量xxx是变量/表达式差一个引号语义完全不同。settings.LOG_LEVEL.upper()是把字符串settings.LOG_LEVEL转大写 SETTINGS.LOG_LEVELsettings.LOG_LEVEL.upper()是访问settings对象的LOG_LEVEL属性、再调它的.upper()方法。改级别改错位置handler 还是 logger我说改 “app” logger 的 levelloggers段下结果改到了 console handler 的 levelhandlers段下。console handler 级别保留 DEBUG终端看 DEBUG 细节“app” logger 级别才该读settings.LOG_LEVEL控制全局消息能否传出。教训改代码前先精确定位改哪一行、改哪个字段别凭印象改。两层过滤里Logger 级别管消息能不能往下传Handler 级别管消息能不能输出——改 Logger 级别才影响全局改 Handler 级别只影响一个出口。pre-commit 因 import 排序挂掉commit 时 ruff 检测到 import 顺序不规范Organize importspre-commit restore 了改动commit 失败。修法是 commit 前先ruff check --fix .修 import 排序再 add commit。教训pre-commit 是故意不让坏代码进 git的设计改完再 commit 是正常流程不是失败。以后 commit 前养成先ruff check --fix .的习惯。五、小结多环境配置让代码不动配置跟环境走结构化日志让print升级成有级别有格式的logging三层架构 异常映射把散装能力串成完整接口。这三块是从能跑的代码往工程化的代码挪的核心动作。路由薄、服务厚、依赖注入——这三层分离让代码可换、可测、可替换。两层过滤Logger 级别 Handler 级别取交集 多环境优先级实例化参数 OS .env 默认值是配置和日志能跟环境走、可动态切的底层机制。后面做任何 LLM / Agent 接口这三层架构 422/503/502/500 四种状态码语义 两层 mock 测试服务层单元 端到端集成都是底层认知提前理清能少踩不少坑。

相关新闻

MrRSS终极指南:3步打造AI智能信息流,告别信息过载

MrRSS终极指南:3步打造AI智能信息流,告别信息过载

MrRSS终极指南:3步打造AI智能信息流,告别信息过载 【免费下载链接】MrRSS A modern, cross-platform, and free AI RSS reader. 一个现代化、跨平台且免费的 AI RSS 阅读器. 项目地址: https://gitcode.com/gh_mirrors/mr/MrRSS 在信息爆炸的时代…

2026/10/12 4:00:18 阅读更多 →
QQ自动签到神器:XAutoDaily使用指南与配置方法

QQ自动签到神器:XAutoDaily使用指南与配置方法

QQ自动签到神器:XAutoDaily使用指南与配置方法 【免费下载链接】XAutoDaily 一个基于QQ的全自动签到模块 项目地址: https://gitcode.com/GitHub_Trending/xa/XAutoDaily 你是否厌倦了每天重复登录QQ完成各种签到任务?会员签到、黄钻打卡、群签到…

2026/10/8 18:18:19 阅读更多 →
Windows系统文件DragDropExperienceDataExchangeDelegated.dll丢失找不到问题解决

Windows系统文件DragDropExperienceDataExchangeDelegated.dll丢失找不到问题解决

在使用电脑系统时经常会出现丢失找不到某些文件的情况,由于很多常用软件都是采用 Microsoft Visual Studio 编写的,所以这类软件的运行需要依赖微软Visual C运行库,比如像 QQ、迅雷、Adobe 软件等等,如果没有安装VC运行库或者安装…

2026/10/12 12:46:06 阅读更多 →

最新新闻

基于JavaWeb的图书馆管理系统:从数据库设计到答辩全流程拆解

基于JavaWeb的图书馆管理系统:从数据库设计到答辩全流程拆解

每年我的邮箱和私信都会被同一个问题塞满:“老师,有没有现成的毕设项目?最好简单点、能跑通、好答辩。”今年那批600套毕设分享出来之后,我翻了一圈,发现图书馆管理系统仍然是出现频率最高的题目之一。说句实话&#x…

2026/10/13 15:59:05 阅读更多 →
VirtualBox VMX源码深度解析:VMCS结构与硬件虚拟化实现

VirtualBox VMX源码深度解析:VMCS结构与硬件虚拟化实现

1. 为什么从VMX切入看VirtualBox源码——不是选最炫的,而是选最“痛”的很多人一看到“VirtualBox源码分析”,第一反应是去翻src/VBox/Devices里那些网卡、显卡模拟器的代码,觉得“看得见摸得着”。我试过,也这么干过——结果两周…

2026/10/13 15:59:05 阅读更多 →
精品水果线上销售网站设计与实现:Spring Boot+Vue全栈电商实战

精品水果线上销售网站设计与实现:Spring Boot+Vue全栈电商实战

1. 内容整体设计与思路拆解做毕业设计选“精品水果线上销售网站”,这个题目乍一看平平无奇,但真正动手做起来,你会发现它其实是把前端展示、后端业务、数据库设计、支付流程、权限控制全串起来的一个完整闭环。我见过太多人选电商类题目最后做…

2026/10/13 15:59:05 阅读更多 →
基于Spring Boot+Vue的精品水果销售网站设计与实现全解析

基于Spring Boot+Vue的精品水果销售网站设计与实现全解析

这段时间正好在帮一位学弟审他的毕业设计,题目就是“精品水果线上销售网站的设计与实现”。说实话,看到这个题目的时候我愣了一下——现在网上商城类的毕设选题太多了,图书、服饰、数码应有尽有,但“精品水果”这个切入点有点意思…

2026/10/13 15:59:05 阅读更多 →
LiteLLM实战:统一大模型API调用与代理网关搭建指南

LiteLLM实战:统一大模型API调用与代理网关搭建指南

如果你和我一样,每天要跟三四个不同的大模型API打交道,可能早就被这一堆SDK和鉴权方式搞得头大了。我一开始是拿LiteLLM当“偷懒工具”用的:它能把OpenAI、Anthropic、Gemini这些五花八门的接口统一成一个Python调用,后来才发现这…

2026/10/13 15:59:05 阅读更多 →
STM32电源闭环控制:ADC触发+双环PID+硬件保护实战

STM32电源闭环控制:ADC触发+双环PID+硬件保护实战

简介:本资源为电子设计大赛(电赛)电源类专项程序代码合集,面向高校电子类专业学生、电赛备赛团队及嵌入式电源系统开发者,聚焦电源管理、闭环控制与硬件协同等核心难点,助力快速构建稳定可靠的电源子系统。…

2026/10/13 15:58:04 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →