validation.mdx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. ---
  2. title: "Input Validation"
  3. description: "Validate tool inputs using Pydantic field and model validators"
  4. icon: "shield-check"
  5. ---
  6. Tool validation ensures data integrity by enforcing schemas before tools execute. Pydantic validators catch invalid input from LLMs, preventing errors and hallucinations from reaching your tool logic.
  7. ## Why Validate Tool Inputs
  8. Without validation, LLMs may provide:
  9. - Malformed data (e.g., text in numeric fields)
  10. - Out-of-range values (e.g., negative ages, future dates in the past)
  11. - Invalid formats (e.g., malformed emails, incorrect URLs)
  12. - Inconsistent field combinations (e.g., mismatched passwords)
  13. Validators enforce constraints at the schema level, giving the LLM immediate feedback to retry with correct inputs.
  14. ## Field Validators
  15. Field validators check individual fields independently. Use `@field_validator` to validate a single field's value.
  16. ### Basic Field Validation
  17. ```python
  18. from pydantic import field_validator
  19. from agency_swarm import BaseTool
  20. class User(BaseTool):
  21. """Create a user account."""
  22. username: str
  23. age: int
  24. @field_validator('username')
  25. @classmethod
  26. def validate_username(cls, value):
  27. if ' ' in value:
  28. raise ValueError('Username must not contain spaces.')
  29. if len(value) < 3:
  30. raise ValueError('Username must be at least 3 characters.')
  31. return value
  32. @field_validator('age')
  33. @classmethod
  34. def validate_age(cls, value):
  35. if value < 0 or value > 120:
  36. raise ValueError('Age must be between 0 and 120.')
  37. return value
  38. def run(self):
  39. return f"Created user: {self.username}, age {self.age}"
  40. ```
  41. ### Multiple Field Validation
  42. Validate multiple fields with a single validator:
  43. ```python
  44. from pydantic import field_validator
  45. from agency_swarm import BaseTool
  46. class ScheduleEvent(BaseTool):
  47. """Schedule a calendar event."""
  48. title: str
  49. description: str
  50. @field_validator('title', 'description')
  51. @classmethod
  52. def validate_not_empty(cls, value):
  53. if not value.strip():
  54. raise ValueError('Field cannot be empty or whitespace.')
  55. return value.strip()
  56. def run(self):
  57. return f"Scheduled: {self.title}"
  58. ```
  59. ### Format Validation
  60. Validate specific formats like URLs or emails:
  61. ```python
  62. from pydantic import field_validator
  63. from agency_swarm import BaseTool
  64. import re
  65. class CreateWebhook(BaseTool):
  66. """Register a webhook URL."""
  67. webhook_url: str
  68. notification_email: str
  69. @field_validator('webhook_url')
  70. @classmethod
  71. def validate_url(cls, value):
  72. url_pattern = r'^https?://.+\..+'
  73. if not re.match(url_pattern, value):
  74. raise ValueError('Must be a valid HTTP/HTTPS URL.')
  75. return value
  76. @field_validator('notification_email')
  77. @classmethod
  78. def validate_email(cls, value):
  79. email_pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$'
  80. if not re.match(email_pattern, value):
  81. raise ValueError('Must be a valid email address.')
  82. return value
  83. def run(self):
  84. return f"Webhook registered: {self.webhook_url}"
  85. ```
  86. ## Model Validators
  87. Model validators validate the entire model, enabling checks across multiple fields. Use `@model_validator` when field validation depends on other fields.
  88. ### Cross-Field Validation
  89. ```python
  90. from pydantic import model_validator
  91. from agency_swarm import BaseTool
  92. class CreateAccount(BaseTool):
  93. """Create a new account with password."""
  94. username: str
  95. password: str
  96. confirm_password: str
  97. @model_validator(mode='after')
  98. def check_passwords_match(self):
  99. if self.password != self.confirm_password:
  100. raise ValueError('Passwords do not match.')
  101. if len(self.password) < 8:
  102. raise ValueError('Password must be at least 8 characters.')
  103. return self
  104. def run(self):
  105. return f"Account created: {self.username}"
  106. ```
  107. ### Date Range Validation
  108. ```python
  109. from datetime import date
  110. from pydantic import model_validator
  111. from agency_swarm import BaseTool
  112. class BookAppointment(BaseTool):
  113. """Book an appointment within a date range."""
  114. start_date: date
  115. end_date: date
  116. reason: str
  117. @model_validator(mode='after')
  118. def validate_date_range(self):
  119. if self.start_date >= self.end_date:
  120. raise ValueError('Start date must be before end date.')
  121. if self.start_date < date.today():
  122. raise ValueError('Cannot book appointments in the past.')
  123. return self
  124. def run(self):
  125. return f"Appointment booked: {self.start_date} to {self.end_date}"
  126. ```
  127. ### Conditional Validation
  128. ```python
  129. from pydantic import model_validator
  130. from agency_swarm import BaseTool
  131. class ProcessPayment(BaseTool):
  132. """Process payment with optional discount code."""
  133. amount: float
  134. discount_code: str | None = None
  135. discount_amount: float | None = None
  136. @model_validator(mode='after')
  137. def validate_discount(self):
  138. if self.discount_code and not self.discount_amount:
  139. raise ValueError('Discount amount required when discount code is provided.')
  140. if self.discount_amount and self.discount_amount >= self.amount:
  141. raise ValueError('Discount cannot exceed payment amount.')
  142. return self
  143. def run(self):
  144. total = self.amount - (self.discount_amount or 0)
  145. return f"Payment processed: ${total:.2f}"
  146. ```
  147. ## Best Practices
  148. <CardGroup cols={2}>
  149. <Card title="Specific Error Messages" icon="message">
  150. Provide clear, actionable error messages that guide the LLM to correct inputs.
  151. </Card>
  152. <Card title="Fail Fast" icon="bolt">
  153. Validate at the schema level before expensive operations or external API calls.
  154. </Card>
  155. <Card title="Normalize Input" icon="arrow-right-arrow-left">
  156. Use validators to clean and normalize data (e.g., strip whitespace, lowercase emails).
  157. </Card>
  158. <Card title="Test Validators" icon="vial">
  159. Write unit tests for validators to ensure they catch invalid cases and allow valid ones.
  160. </Card>
  161. </CardGroup>
  162. ## See Also
  163. <CardGroup cols={2}>
  164. <Card title="Tool Best Practices" icon="lightbulb" href="/core-framework/tools/custom-tools/best-practices">
  165. General best practices for tool design
  166. </Card>
  167. <Card title="Pydantic Validators" icon="link" href="https://docs.pydantic.dev/latest/usage/validators/">
  168. Official Pydantic validators documentation
  169. </Card>
  170. <Card title="Agent Guardrails" icon="shield-halved" href="/additional-features/guardrails/overview">
  171. Validate agent inputs and outputs with guardrails
  172. </Card>
  173. <Card title="Tool Configuration" icon="gear" href="/core-framework/tools/custom-tools/configuration">
  174. Configure tool behavior and parameters
  175. </Card>
  176. </CardGroup>