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 13 kB

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