You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

validation.py 6.8 kB

Support Component Validation API in AGS (#5503) <!-- Thank you for your contribution! Please review https://microsoft.github.io/autogen/docs/Contribute before opening a pull request. --> <!-- Please add a reviewer to the assignee section when you create a PR. If you don't have the access to it, we will shortly find a reviewer and assign them to your PR. --> ## Why are these changes needed? It is useful to rapidly validate any changes to a team structure as teams are built either via drag and drop or by modifying the underlying spec You can now “validate” your team. The key ideas are as follows - Each team is based on some Component Config specification which is a pedantic model underneath. - Validation is 3 pronged based on a ValidatorService class - Data model validation (validate component schema) - Instantiation validation (validate component can be instantiated) - Provider validation, component_type validation (validate that provider exists and can be imported) - UX: each time a component is **loaded or saved**, it is automatically validated and any errors shown (via a server endpoint). This way, the developer immediately knows if updates to the configuration is wrong or has errors. > Note: this is different from actually running the component against a task. Currently you can run the entire team. In a separate PR we will implement ability to run/test other components. <img width="1360" alt="image" src="https://github.com/user-attachments/assets/d61095b7-0b07-463a-b4b2-5c50ded750f6" /> <img width="1368" alt="image" src="https://github.com/user-attachments/assets/09a1677e-76e8-44a4-9749-15c27457efbb" /> <!-- Please give a short summary of the change and the problem this solves. --> ## Related issue number Closes #4616 <!-- For example: "Closes #1234" --> ## Checks - [ ] I've included any doc changes needed for https://microsoft.github.io/autogen/. See https://microsoft.github.io/autogen/docs/Contribute#documentation to build and test documentation locally. - [ ] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [ ] I've made sure all auto checks have passed.
1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. # api/routes/validation.py
  2. import importlib
  3. from typing import Any, Dict, List, Optional
  4. from autogen_core import ComponentModel, is_component_class
  5. from fastapi import APIRouter, HTTPException
  6. from pydantic import BaseModel
  7. router = APIRouter()
  8. class ValidationRequest(BaseModel):
  9. component: Dict[str, Any]
  10. class ValidationError(BaseModel):
  11. field: str
  12. error: str
  13. suggestion: Optional[str] = None
  14. class ValidationResponse(BaseModel):
  15. is_valid: bool
  16. errors: List[ValidationError] = []
  17. warnings: List[ValidationError] = []
  18. class ValidationService:
  19. @staticmethod
  20. def validate_provider(provider: str) -> Optional[ValidationError]:
  21. """Validate that the provider exists and can be imported"""
  22. try:
  23. if provider in ["azure_openai_chat_completion_client", "AzureOpenAIChatCompletionClient"]:
  24. provider = "autogen_ext.models.openai.AzureOpenAIChatCompletionClient"
  25. elif provider in ["openai_chat_completion_client", "OpenAIChatCompletionClient"]:
  26. provider = "autogen_ext.models.openai.OpenAIChatCompletionClient"
  27. module_path, class_name = provider.rsplit(".", maxsplit=1)
  28. module = importlib.import_module(module_path)
  29. component_class = getattr(module, class_name)
  30. if not is_component_class(component_class):
  31. return ValidationError(
  32. field="provider",
  33. error=f"Class {provider} is not a valid component class",
  34. suggestion="Ensure the class inherits from Component and implements required methods",
  35. )
  36. return None
  37. except ImportError:
  38. return ValidationError(
  39. field="provider",
  40. error=f"Could not import provider {provider}",
  41. suggestion="Check that the provider module is installed and the path is correct",
  42. )
  43. except Exception as e:
  44. return ValidationError(
  45. field="provider",
  46. error=f"Error validating provider: {str(e)}",
  47. suggestion="Check the provider string format and class implementation",
  48. )
  49. @staticmethod
  50. def validate_component_type(component: Dict[str, Any]) -> Optional[ValidationError]:
  51. """Validate the component type"""
  52. if "component_type" not in component:
  53. return ValidationError(
  54. field="component_type",
  55. error="Component type is missing",
  56. suggestion="Add a component_type field to the component configuration",
  57. )
  58. return None
  59. @staticmethod
  60. def validate_config_schema(component: Dict[str, Any]) -> List[ValidationError]:
  61. """Validate the component configuration against its schema"""
  62. errors = []
  63. try:
  64. # Convert to ComponentModel for initial validation
  65. model = ComponentModel(**component)
  66. # Get the component class
  67. provider = model.provider
  68. module_path, class_name = provider.rsplit(".", maxsplit=1)
  69. module = importlib.import_module(module_path)
  70. component_class = getattr(module, class_name)
  71. # Validate against component's schema
  72. if hasattr(component_class, "component_config_schema"):
  73. try:
  74. component_class.component_config_schema.model_validate(model.config)
  75. except Exception as e:
  76. errors.append(
  77. ValidationError(
  78. field="config",
  79. error=f"Config validation failed: {str(e)}",
  80. suggestion="Check that the config matches the component's schema",
  81. )
  82. )
  83. else:
  84. errors.append(
  85. ValidationError(
  86. field="config",
  87. error="Component class missing config schema",
  88. suggestion="Implement component_config_schema in the component class",
  89. )
  90. )
  91. except Exception as e:
  92. errors.append(
  93. ValidationError(
  94. field="config",
  95. error=f"Schema validation error: {str(e)}",
  96. suggestion="Check the component configuration format",
  97. )
  98. )
  99. return errors
  100. @staticmethod
  101. def validate_instantiation(component: Dict[str, Any]) -> Optional[ValidationError]:
  102. """Validate that the component can be instantiated"""
  103. try:
  104. model = ComponentModel(**component)
  105. # Attempt to load the component
  106. module_path, class_name = model.provider.rsplit(".", maxsplit=1)
  107. module = importlib.import_module(module_path)
  108. component_class = getattr(module, class_name)
  109. component_class.load_component(model)
  110. return None
  111. except Exception as e:
  112. return ValidationError(
  113. field="instantiation",
  114. error=f"Failed to instantiate component: {str(e)}",
  115. suggestion="Check that the component can be properly instantiated with the given config",
  116. )
  117. @classmethod
  118. def validate(cls, component: Dict[str, Any]) -> ValidationResponse:
  119. """Validate a component configuration"""
  120. errors = []
  121. warnings = []
  122. # Check provider
  123. if provider_error := cls.validate_provider(component.get("provider", "")):
  124. errors.append(provider_error)
  125. # Check component type
  126. if type_error := cls.validate_component_type(component):
  127. errors.append(type_error)
  128. # Validate schema
  129. schema_errors = cls.validate_config_schema(component)
  130. errors.extend(schema_errors)
  131. # Only attempt instantiation if no errors so far
  132. if not errors:
  133. if inst_error := cls.validate_instantiation(component):
  134. errors.append(inst_error)
  135. # Check for version warnings
  136. if "version" not in component:
  137. warnings.append(
  138. ValidationError(
  139. field="version",
  140. error="Component version not specified",
  141. suggestion="Consider adding a version to ensure compatibility",
  142. )
  143. )
  144. return ValidationResponse(is_valid=len(errors) == 0, errors=errors, warnings=warnings)
  145. @router.post("/")
  146. async def validate_component(request: ValidationRequest) -> ValidationResponse:
  147. """Validate a component configuration"""
  148. return ValidationService.validate(request.component)