diet_schemas.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. """
  2. 多 Agent 饮食流水线:各阶段固定输出 Schema(Pydantic v2)。
  3. """
  4. from __future__ import annotations
  5. from typing import List
  6. from pydantic import BaseModel, ConfigDict, Field, field_validator
  7. SCHEMA_VERSION = "2"
  8. class FoodItem(BaseModel):
  9. model_config = ConfigDict(extra="forbid")
  10. meal_time: str = Field(default="", max_length=40)
  11. food_name: str = Field(min_length=1, max_length=120)
  12. portion_text: str = Field(min_length=1, max_length=120)
  13. confidence: float = Field(default=0.7, ge=0, le=1)
  14. class NutritionSummary(BaseModel):
  15. model_config = ConfigDict(extra="forbid")
  16. protein_g: float = Field(default=0, ge=0, le=800)
  17. carb_g: float = Field(default=0, ge=0, le=1200)
  18. fat_g: float = Field(default=0, ge=0, le=800)
  19. fiber_g: float = Field(default=0, ge=0, le=300)
  20. sodium_mg: float = Field(default=0, ge=0, le=20000)
  21. calories_kcal: float = Field(default=0, ge=0, le=12000)
  22. class FoodParseOutput(BaseModel):
  23. """饮食日志解析:由 LLM 从自由文本抽取食物条目并估算营养。"""
  24. model_config = ConfigDict(extra="forbid")
  25. items: List[FoodItem] = Field(default_factory=list, max_length=40)
  26. nutrition_summary: NutritionSummary = Field(default_factory=NutritionSummary)
  27. parse_notes: str = Field(default="", max_length=1200)
  28. class MealPlanItem(BaseModel):
  29. model_config = ConfigDict(extra="forbid")
  30. name: str = Field(min_length=1, max_length=120)
  31. portion: str = Field(min_length=1, max_length=220)
  32. est_protein_g: float = Field(ge=0, le=250)
  33. why: str = Field(default="", max_length=600)
  34. class MealPlan(BaseModel):
  35. model_config = ConfigDict(extra="forbid")
  36. items: List[MealPlanItem] = Field(min_length=1, max_length=15)
  37. total_est_protein_g: float = Field(ge=0, le=500)
  38. tips: List[str] = Field(default_factory=list, max_length=12)
  39. @field_validator("tips")
  40. @classmethod
  41. def cap_tip_len(cls, v: List[str]) -> List[str]:
  42. return [t.strip()[:500] for t in v if t and t.strip()][:12]
  43. class NutritionistOutput(BaseModel):
  44. """营养师 Agent:缺口与检索方向。"""
  45. model_config = ConfigDict(extra="forbid")
  46. protein_gap_g: float = Field(ge=0, le=400)
  47. rationale: str = Field(min_length=4, max_length=2000)
  48. suggested_lookup_queries: List[str] = Field(min_length=1, max_length=10)
  49. candidate_focus: List[str] = Field(default_factory=list, max_length=10)
  50. @field_validator("suggested_lookup_queries")
  51. @classmethod
  52. def v_queries(cls, v: List[str]) -> List[str]:
  53. out = [str(s).strip() for s in v if s and str(s).strip()]
  54. if not out:
  55. raise ValueError("至少提供一条 suggested_lookup_queries")
  56. return out[:10]
  57. @field_validator("candidate_focus")
  58. @classmethod
  59. def v_focus(cls, v: List[str]) -> List[str]:
  60. return [str(s).strip() for s in v if s and str(s).strip()][:10]
  61. class CoachOutput(BaseModel):
  62. """运动恢复 Coach:时间与恢复约束。"""
  63. model_config = ConfigDict(extra="forbid")
  64. training_recovery_note: str = Field(min_length=4, max_length=2000)
  65. timing_constraints: str = Field(min_length=4, max_length=1200)
  66. energy_note: str = Field(default="", max_length=1200)
  67. coach_constraints_for_menu: List[str] = Field(default_factory=list, max_length=12)
  68. class HabitOutput(BaseModel):
  69. """习惯 Agent:对齐 Reflect + 最终可执行菜单。"""
  70. model_config = ConfigDict(extra="forbid")
  71. reflect_alignment: str = Field(min_length=4, max_length=2000)
  72. execution_hints: List[str] = Field(default_factory=list, max_length=12)
  73. meal_plan: MealPlan
  74. @field_validator("execution_hints")
  75. @classmethod
  76. def strip_hints(cls, v: List[str]) -> List[str]:
  77. return [t.strip()[:400] for t in v if t and t.strip()][:12]