# Credit API Pydantic UUID 兼容性修复 **日期**: 2026-01-28 **类型**: Bug Fix **影响范围**: Credit API - 积分消耗端点 ## 问题描述 `POST /api/v1/credits/consume` 端点在返回响应时出现 Pydantic 验证错误: ``` pydantic_core._pydantic_core.ValidationError: 2 validation errors for CreditConsumptionResponse consumption_id UUID input should be a string, bytes or UUID object [type=uuid_type, input_value=UUID('...'), input_type=UUID] user_id UUID input should be a string, bytes or UUID object [type=uuid_type, input_value=UUID('...'), input_type=UUID] ``` ## 根本原因 SQLModel + Pydantic v2 的兼容性问题: 1. SQLModel 的 UUID 字段在 ORM 对象中是 `uuid.UUID` 类型 2. Pydantic v2 在验证时对 UUID 类型有严格的类型检查 3. 使用 `from_orm()` 或 `model_validate()` 时,UUID 对象无法正确转换 4. `model_validator(mode='before')` 在接收 UUID 对象时也会报错 ## 解决方案 在 API 层手动构造响应字典,将 UUID 对象转换为字符串: ```python # server/app/api/v1/credits.py @router.post("/consume", response_model=SuccessResponse[CreditConsumptionResponse]) async def consume_credits(...): consumption_log = await service.consume_credits(...) # 手动构造响应字典,避免 SQLModel + Pydantic v2 的 UUID 兼容性问题 from app.services.credit_service import FeatureType, TaskStatus response_dict = { "consumptionId": str(consumption_log.consumption_id), "userId": str(consumption_log.user_id), "featureType": consumption_log.feature_type, "featureTypeName": FeatureType.to_name(consumption_log.feature_type), "creditsConsumed": consumption_log.credits_consumed, "taskId": consumption_log.task_id, "taskStatus": consumption_log.task_status, "taskStatusName": TaskStatus.to_name(consumption_log.task_status), "aiJobId": str(consumption_log.ai_job_id) if consumption_log.ai_job_id else None, "resourceId": str(consumption_log.resource_id) if consumption_log.resource_id else None, "resourceType": consumption_log.resource_type, "createdAt": consumption_log.created_at, "completedAt": consumption_log.completed_at } response_data = CreditConsumptionResponse(**response_dict) return SuccessResponse(data=response_data) ``` 同时修改 schema 的 `model_validator` 从 `mode='before'` 改为 `mode='after'`: ```python # server/app/schemas/credit.py class CreditConsumptionResponse(BaseModel): @model_validator(mode='after') def compute_names(self) -> 'CreditConsumptionResponse': """计算枚举名称字段(在验证后执行)""" from app.services.credit_service import FeatureType, TaskStatus if not self.feature_type_name: self.feature_type_name = FeatureType.to_name(self.feature_type) if not self.task_status_name: self.task_status_name = TaskStatus.to_name(self.task_status) return self ``` ## 修改文件 - `server/app/api/v1/credits.py` - 修改 `consume_credits` 端点 - `server/app/schemas/credit.py` - 修改 `CreditConsumptionResponse` validator ## 测试结果 ✅ 所有 9 个 Credit API 集成测试通过: ```bash tests/integration/test_credit_api.py::TestCreditBalanceAPI::test_get_balance PASSED tests/integration/test_credit_api.py::TestCreditTransactionAPI::test_get_transactions PASSED tests/integration/test_credit_api.py::TestCreditTransactionAPI::test_get_transactions_with_filter PASSED tests/integration/test_credit_api.py::TestCreditConsumptionAPI::test_consume_credits PASSED tests/integration/test_credit_api.py::TestCreditConsumptionAPI::test_consume_credits_insufficient PASSED tests/integration/test_credit_api.py::TestCreditConsumptionAPI::test_get_consumption_logs PASSED tests/integration/test_credit_api.py::TestCreditPackageAPI::test_get_packages PASSED tests/integration/test_credit_api.py::TestCreditPackageAPI::test_get_package_detail PASSED tests/integration/test_credit_api.py::TestCreditCalculationAPI::test_calculate_credits PASSED ``` ## 经验总结 1. **SQLModel + Pydantic v2 兼容性**:在 API 层返回响应时,需要手动处理 UUID 字段 2. **最佳实践**:对于复杂的响应模型,手动构造字典比使用 `from_orm()` 更可靠 3. **model_validator 时机**:`mode='after'` 在字段验证后执行,更适合计算派生字段 4. **测试驱动**:集成测试能够及时发现 ORM 到 Pydantic 的序列化问题 ## 相关文档 - [Pydantic v2 Migration Guide](https://docs.pydantic.dev/latest/migration/) - [SQLModel with Pydantic v2](https://sqlmodel.tiangolo.com/)