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.

config_manager.py 12 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. import logging
  2. from pathlib import Path
  3. from typing import Any, Dict, List, Optional, Union
  4. from loguru import logger
  5. from ..datamodel.db import Agent, LinkTypes, Model, Team, Tool
  6. from ..datamodel.types import ComponentConfigInput, ComponentTypes, Response
  7. from .component_factory import ComponentFactory
  8. from .db_manager import DatabaseManager
  9. class ConfigurationManager:
  10. """Manages persistence and relationships of components using ComponentFactory for validation"""
  11. DEFAULT_UNIQUENESS_FIELDS = {
  12. ComponentTypes.MODEL: ["model_type", "model"],
  13. ComponentTypes.TOOL: ["name"],
  14. ComponentTypes.AGENT: ["agent_type", "name"],
  15. ComponentTypes.TEAM: ["team_type", "name"],
  16. }
  17. def __init__(self, db_manager: DatabaseManager, uniqueness_fields: Dict[ComponentTypes, List[str]] = None):
  18. self.db_manager = db_manager
  19. self.component_factory = ComponentFactory()
  20. self.uniqueness_fields = uniqueness_fields or self.DEFAULT_UNIQUENESS_FIELDS
  21. async def import_component(
  22. self, component_config: ComponentConfigInput, user_id: str, check_exists: bool = False
  23. ) -> Response:
  24. """
  25. Import a component configuration, validate it, and store the resulting component.
  26. Args:
  27. component_config: Configuration for the component (file path, dict, or ComponentConfig)
  28. user_id: User ID to associate with imported component
  29. check_exists: Whether to check for existing components before storing (default: False)
  30. Returns:
  31. Response containing import results or error
  32. """
  33. try:
  34. # Get validated config as dict
  35. config = await self.component_factory.load(component_config, return_type="dict")
  36. # Get component type
  37. component_type = self._determine_component_type(config)
  38. if not component_type:
  39. raise ValueError("Unable to determine component type from config")
  40. # Check existence if requested
  41. if check_exists:
  42. existing = self._check_exists(component_type, config, user_id)
  43. if existing:
  44. return Response(
  45. message=self._format_exists_message(component_type, config),
  46. status=True,
  47. data={"id": existing.id},
  48. )
  49. # Route to appropriate storage method
  50. if component_type == ComponentTypes.TEAM:
  51. return await self._store_team(config, user_id, check_exists)
  52. elif component_type == ComponentTypes.AGENT:
  53. return await self._store_agent(config, user_id, check_exists)
  54. elif component_type == ComponentTypes.MODEL:
  55. return await self._store_model(config, user_id)
  56. elif component_type == ComponentTypes.TOOL:
  57. return await self._store_tool(config, user_id)
  58. else:
  59. raise ValueError(f"Unsupported component type: {component_type}")
  60. except Exception as e:
  61. logger.error(f"Failed to import component: {str(e)}")
  62. return Response(message=str(e), status=False)
  63. async def import_directory(self, directory: Union[str, Path], user_id: str, check_exists: bool = False) -> Response:
  64. """
  65. Import all component configurations from a directory.
  66. Args:
  67. directory: Path to directory containing configuration files
  68. user_id: User ID to associate with imported components
  69. check_exists: Whether to check for existing components before storing (default: False)
  70. Returns:
  71. Response containing import results for all files
  72. """
  73. try:
  74. configs = await self.component_factory.load_directory(directory, return_type="dict")
  75. results = []
  76. for config in configs:
  77. result = await self.import_component(config, user_id, check_exists)
  78. results.append(
  79. {
  80. "component": self._get_component_type(config),
  81. "status": result.status,
  82. "message": result.message,
  83. "id": result.data.get("id") if result.status else None,
  84. }
  85. )
  86. return Response(message="Directory import complete", status=True, data=results)
  87. except Exception as e:
  88. logger.error(f"Failed to import directory: {str(e)}")
  89. return Response(message=str(e), status=False)
  90. async def _store_team(self, config: dict, user_id: str, check_exists: bool = False) -> Response:
  91. """Store team component and manage its relationships with agents"""
  92. try:
  93. # Store the team
  94. team_db = Team(user_id=user_id, config=config)
  95. team_result = self.db_manager.upsert(team_db)
  96. if not team_result.status:
  97. return team_result
  98. team_id = team_result.data["id"]
  99. # Handle participants (agents)
  100. for participant in config.get("participants", []):
  101. if check_exists:
  102. # Check for existing agent
  103. agent_type = self._determine_component_type(participant)
  104. existing_agent = self._check_exists(agent_type, participant, user_id)
  105. if existing_agent:
  106. # Link existing agent
  107. self.db_manager.link(LinkTypes.TEAM_AGENT, team_id, existing_agent.id)
  108. logger.info(f"Linked existing agent to team: {existing_agent}")
  109. continue
  110. # Store and link new agent
  111. agent_result = await self._store_agent(participant, user_id, check_exists)
  112. if agent_result.status:
  113. self.db_manager.link(LinkTypes.TEAM_AGENT, team_id, agent_result.data["id"])
  114. return team_result
  115. except Exception as e:
  116. logger.error(f"Failed to store team: {str(e)}")
  117. return Response(message=str(e), status=False)
  118. async def _store_agent(self, config: dict, user_id: str, check_exists: bool = False) -> Response:
  119. """Store agent component and manage its relationships with tools and model"""
  120. try:
  121. # Store the agent
  122. agent_db = Agent(user_id=user_id, config=config)
  123. agent_result = self.db_manager.upsert(agent_db)
  124. if not agent_result.status:
  125. return agent_result
  126. agent_id = agent_result.data["id"]
  127. # Handle model client
  128. if "model_client" in config:
  129. if check_exists:
  130. # Check for existing model
  131. model_type = self._determine_component_type(config["model_client"])
  132. existing_model = self._check_exists(model_type, config["model_client"], user_id)
  133. if existing_model:
  134. # Link existing model
  135. self.db_manager.link(LinkTypes.AGENT_MODEL, agent_id, existing_model.id)
  136. logger.info(f"Linked existing model to agent: {existing_model.config.model_type}")
  137. else:
  138. # Store and link new model
  139. model_result = await self._store_model(config["model_client"], user_id)
  140. if model_result.status:
  141. self.db_manager.link(LinkTypes.AGENT_MODEL, agent_id, model_result.data["id"])
  142. else:
  143. # Store and link new model without checking
  144. model_result = await self._store_model(config["model_client"], user_id)
  145. if model_result.status:
  146. self.db_manager.link(LinkTypes.AGENT_MODEL, agent_id, model_result.data["id"])
  147. # Handle tools
  148. for tool_config in config.get("tools", []):
  149. if check_exists:
  150. # Check for existing tool
  151. tool_type = self._determine_component_type(tool_config)
  152. existing_tool = self._check_exists(tool_type, tool_config, user_id)
  153. if existing_tool:
  154. # Link existing tool
  155. self.db_manager.link(LinkTypes.AGENT_TOOL, agent_id, existing_tool.id)
  156. logger.info(f"Linked existing tool to agent: {existing_tool.config.name}")
  157. continue
  158. # Store and link new tool
  159. tool_result = await self._store_tool(tool_config, user_id)
  160. if tool_result.status:
  161. self.db_manager.link(LinkTypes.AGENT_TOOL, agent_id, tool_result.data["id"])
  162. return agent_result
  163. except Exception as e:
  164. logger.error(f"Failed to store agent: {str(e)}")
  165. return Response(message=str(e), status=False)
  166. async def _store_model(self, config: dict, user_id: str) -> Response:
  167. """Store model component (leaf node - no relationships)"""
  168. try:
  169. model_db = Model(user_id=user_id, config=config)
  170. return self.db_manager.upsert(model_db)
  171. except Exception as e:
  172. logger.error(f"Failed to store model: {str(e)}")
  173. return Response(message=str(e), status=False)
  174. async def _store_tool(self, config: dict, user_id: str) -> Response:
  175. """Store tool component (leaf node - no relationships)"""
  176. try:
  177. tool_db = Tool(user_id=user_id, config=config)
  178. return self.db_manager.upsert(tool_db)
  179. except Exception as e:
  180. logger.error(f"Failed to store tool: {str(e)}")
  181. return Response(message=str(e), status=False)
  182. def _check_exists(
  183. self, component_type: ComponentTypes, config: dict, user_id: str
  184. ) -> Optional[Union[Model, Tool, Agent, Team]]:
  185. """Check if component exists based on configured uniqueness fields."""
  186. fields = self.uniqueness_fields.get(component_type, [])
  187. if not fields:
  188. return None
  189. component_class = {
  190. ComponentTypes.MODEL: Model,
  191. ComponentTypes.TOOL: Tool,
  192. ComponentTypes.AGENT: Agent,
  193. ComponentTypes.TEAM: Team,
  194. }.get(component_type)
  195. components = self.db_manager.get(component_class, {"user_id": user_id}).data
  196. for component in components:
  197. matches = all(component.config.get(field) == config.get(field) for field in fields)
  198. if matches:
  199. return component
  200. return None
  201. def _format_exists_message(self, component_type: ComponentTypes, config: dict) -> str:
  202. """Format existence message with identifying fields."""
  203. fields = self.uniqueness_fields.get(component_type, [])
  204. field_values = [f"{field}='{config.get(field)}'" for field in fields]
  205. return f"{component_type.value} with {' and '.join(field_values)} already exists"
  206. def _determine_component_type(self, config: dict) -> Optional[ComponentTypes]:
  207. """Determine component type from configuration dictionary"""
  208. if "team_type" in config:
  209. return ComponentTypes.TEAM
  210. elif "agent_type" in config:
  211. return ComponentTypes.AGENT
  212. elif "model_type" in config:
  213. return ComponentTypes.MODEL
  214. elif "tool_type" in config:
  215. return ComponentTypes.TOOL
  216. return None
  217. def _get_component_type(self, config: dict) -> str:
  218. """Helper to get component type string from config"""
  219. component_type = self._determine_component_type(config)
  220. return component_type.value if component_type else "unknown"
  221. async def cleanup(self):
  222. """Cleanup resources"""
  223. await self.component_factory.cleanup()