
前几篇我们把 Agent 包成了 FastAPI 接口(第 4 篇),加了字段校验、接了错误处理。功能都齐了。但有两个问题一直卡着我:
一是看不清。 有一次模型调用失败,前端只返回一句"模型调用失败",我盯着屏幕却不知道:请求到底进没进来?Service 执行到哪一步?具体炸在哪一行?服务能跑,但看不清它怎么跑。
二是改不动。 想给分析加个缓存,发现 Agent 调用、错误处理、日志全挤在一块,只能往 routers.py 里硬塞。能跑,但不等于好维护。
所以这一篇同时解决这两件事:把项目分层,再加日志。
能跑只是 Demo,能分层、能观察,才开始像可维护的服务。
分层之后,目录是这样:
app/
├── main.py 应用入口
├── routers.py API 接口层
├── schemas.py 请求和响应模型
└── services.py 业务服务层
resume_analyzer/
├── agent.py PydanticAI Agent 调用
├── models.py Agent 输出结构
└── tools.py Agent 工具函数app/ 管 Web 服务和接口,resume_analyzer/ 管 AI 能力本身。两条线分开,后面换模型、加工具都不动接口层。
main.py —— 启动器不碰业务。只做:创建 FastAPI app、挂载 router、配全局日志、提供 health check。
routers.py —— 接口层只关心 HTTP:地址是什么、请求/响应模型是什么、异常返回什么状态码。不该知道 Agent 内部细节。
schemas.py —— 契约层放 AnalyzeRequest / AnalyzeResponse。它告诉调用方:该传什么、会返回什么。前端对接就靠它。
services.py —— 业务层负责业务编排:调 Agent、记日志,以后还能加缓存、重试、数据库、权限。
agent.py —— AI 能力层只管 PydanticAI:定义 Agent、调模型、回结构化结果。
一句话类比:main 是门面,routers 是前台,schemas 是表单,services 是后台办事员,agent 是里面真正干活的专家。各司其职,谁也别越界。
它可能现在只是这样:
def analyze_resume_service(resume_text: str) -> ResumeAnalysis:
return analyze_resume(resume_text)看着只是"包了一层",没干啥。但它的价值不在今天,在以后:
日志 → 放这
缓存 → 放这
重试 → 放这
数据库保存 → 放这
权限判断 → 放这
调用耗时 → 放这它的存在,让 routers.py 永远只说"收到请求、返回结果",不用面对复杂业务。
解决了分层,还得能看清服务内部。AI 服务跑起来后,出问题的地方比普通接口多:
没有日志时,用户只看到:
{
"detail": {
"code": "MODEL_CALL_FAILED",
"message": "模型调用失败"
}
}这对用户友好,对开发者不够。开发者还需要知道:哪一次请求失败了、失败前执行到了哪、具体异常堆栈是什么。日志就是干这个的。
API 返回给用户看,日志留给开发者看。 两者面对的人不同,该说的内容也不同。
app/main.py —— 定规则,不写日志main.py 是入口,在这里配置全局日志格式:
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s - %(message)s",
)它规定的格式是:时间 等级 模块名 - 内容。比如一次真实运行:
2026-08-14 10:20:01 INFO app.services - 开始分析简历,文本长度=61app/services.py —— 记业务,才真正写日志业务层拿到 logger 后,按执行过程记录:
import logging
logger = logging.getLogger(__name__)logger.info("开始分析简历,文本长度=%s", len(resume_text))
# ... 调用 Agent ...
logger.info("简历分析成功")失败处:
logger.exception("简历分析失败")这里有个容易混的点。main.py 的 basicConfig(...) 不是在写日志,是在定规则——它告诉整个项目显示什么等级、长什么样、带不带模块名。而 services.py 的 logger.info(...) 才记录具体的事。
main.py 配置日志规则(全局一次)
services.py 记录业务日志(每次执行)这是 Python 服务端最常见的组织方式:规则集中定,业务分散记。
print() 是临时调试,能看但不适合长期维护:
print("开始分析")logging 是正式记录,优势是结构化的:
logger.info("开始分析简历,文本长度=%s", len(resume_text))它有四样 print 没有的东西:
服务端项目里,优先用 logging。
这个方法必须放在 except 里:
try:
result = analyze_resume(resume_text)
except Exception:
logger.exception("简历分析失败")
raise它的特别之处:不仅记错误信息,还自动带上 traceback。逻辑是——先把详细错误写进日志,再把异常继续抛给上层。routers.py 再把它转成结构化的 API 错误。
一句记住:logger.exception = 记错误 + 记堆栈 + 重新抛出。
今天最重要的理解就一句:
错误响应:给用户 / 前端看
日志记录:给开发者看模型调用失败时,前端看到的是干净的:
{
"detail": {
"code": "MODEL_CALL_FAILED",
"message": "模型调用失败",
"hint": "请检查 API Key、模型名称、网络连接或上游模型服务状态"
}
}开发者在终端看到的是原始的:
ERROR app.services - 简历分析失败
Traceback (most recent call last):
...这就是分工的价值:用户体验不破,排查线索不丢。
一个 AI 服务不能只会调模型,还要有清晰的分层、稳定的错误边界、能观察运行过程的日志。
回顾这 5 篇走过的路,项目已经具备:
结构化输出 (1) output_type
字段校验 (2) field_validator
工具调用 (2) Tool
Schema/Evidence (3)
API 封装 (4) FastAPI
错误处理 (4) HTTPException
工程分层 (5) services.py
日志记录 (5) logging这已经是一个小型 AI 后端项目的雏形。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。