Coverage for mindsdb / interfaces / chatbot / chatbot_controller.py: 66%

140 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-21 00:36 +0000

1from typing import Dict, List 

2 

3from mindsdb.api.executor.controllers.session_controller import SessionController 

4from mindsdb.interfaces.agents.agents_controller import AgentsController 

5from mindsdb.interfaces.chatbot.chatbot_task import ChatBotTask 

6from mindsdb.interfaces.database.projects import ProjectController 

7from mindsdb.interfaces.storage import db 

8from mindsdb.interfaces.model.functions import get_project_records 

9from mindsdb.utilities.exception import EntityNotExistsError 

10from mindsdb.utilities.context import context as ctx 

11from mindsdb.utilities.config import config 

12 

13 

14default_project = config.get('default_project') 

15 

16 

17class ChatBotController: 

18 '''Handles CRUD operations at the database level for Chatbots''' 

19 

20 OBJECT_TYPE = 'chatbot' 

21 

22 def __init__(self, project_controller: ProjectController = None, agents_controller: AgentsController = None): 

23 if project_controller is None: 23 ↛ 25line 23 didn't jump to line 25 because the condition on line 23 was always true

24 project_controller = ProjectController() 

25 if agents_controller is None: 25 ↛ 27line 25 didn't jump to line 27 because the condition on line 25 was always true

26 agents_controller = AgentsController() 

27 self.project_controller = project_controller 

28 self.agents_controller = agents_controller 

29 

30 def get_chatbot(self, chatbot_name: str, project_name: str = default_project) -> dict: 

31 ''' 

32 Gets a chatbot by name. 

33 

34 Parameters: 

35 chatbot_name (str): The name of the chatbot 

36 project_name (str): The name of the containing project 

37 

38 Returns: 

39 bot (db.ChatBots): The database chatbot object 

40 ''' 

41 

42 project = self.project_controller.get(name=project_name) 

43 

44 query = db.session.query( 

45 db.ChatBots, db.Tasks 

46 ).join( 

47 db.Tasks, db.ChatBots.id == db.Tasks.object_id 

48 ).filter( 

49 db.ChatBots.name == chatbot_name, 

50 db.ChatBots.project_id == project.id, 

51 db.Tasks.object_type == self.OBJECT_TYPE, 

52 db.Tasks.company_id == ctx.company_id, 

53 ) 

54 

55 return self._get_chatbot(query, project) 

56 

57 def get_chatbot_by_id(self, chatbot_id: int) -> dict: 

58 ''' 

59 Gets a chatbot by id. 

60 

61 Parameters: 

62 chatbot_id (int): The id of the chatbot 

63 

64 Returns: 

65 bot (db.ChatBots): The database chatbot object 

66 ''' 

67 

68 query = db.session.query( 

69 db.ChatBots, db.Tasks 

70 ).join( 

71 db.Tasks, db.ChatBots.id == db.Tasks.object_id 

72 ).filter( 

73 db.ChatBots.id == chatbot_id, 

74 db.Tasks.object_type == self.OBJECT_TYPE, 

75 db.Tasks.company_id == ctx.company_id, 

76 ) 

77 

78 return self._get_chatbot(query) 

79 

80 def _get_chatbot(self, query, project: db.Project = None) -> dict: 

81 ''' 

82 Gets a chatbot by query. 

83 

84 Parameters: 

85 query: The query to get the chatbot 

86 

87 Returns: 

88 bot (db.ChatBots): The database chatbot object 

89 ''' 

90 

91 query_result = query.first() 

92 if query_result is None: 

93 return None 

94 bot, task = query_result 

95 

96 # Include DB, Agent, and Task information in response. 

97 session = SessionController() 

98 database_names = { 

99 i['id']: i['name'] 

100 for i in session.database_controller.get_list() 

101 } 

102 

103 agent = self.agents_controller.get_agent_by_id(bot.agent_id) 

104 agent_obj = agent.as_dict() if agent is not None else None 

105 

106 bot_obj = { 

107 'id': bot.id, 

108 'name': bot.name, 

109 'project': project.name if project else self.project_controller.get(bot.project_id).name, 

110 'agent': agent_obj, 

111 'database_id': bot.database_id, # TODO remove in future 

112 'database': database_names.get(bot.database_id, '?'), 

113 'model_name': bot.model_name, 

114 'params': bot.params, 

115 'created_at': bot.created_at, 

116 'is_running': task.active, 

117 'last_error': task.last_error, 

118 'webhook_token': bot.webhook_token, 

119 } 

120 

121 return bot_obj 

122 

123 def get_chatbots(self, project_name: str = default_project) -> List[dict]: 

124 ''' 

125 Gets all chatbots in a project. 

126 

127 Parameters: 

128 project_name (str): The name of the containing project. If None, then return from all projects 

129 

130 Returns: 

131 all_bots (List[db.ChatBots]): List of database chatbot object 

132 ''' 

133 

134 project_names = {} 

135 for project in get_project_records(): 

136 if project_name is not None and project.name != project_name: 

137 continue 

138 project_names[project.id] = project.name 

139 

140 if project_name is not None and project_name not in project_names.values(): 

141 raise EntityNotExistsError(f'Project {project_name} not found') 

142 

143 query = db.session.query( 

144 db.ChatBots, db.Tasks 

145 ).join( 

146 db.Tasks, db.ChatBots.id == db.Tasks.object_id 

147 ).filter( 

148 db.ChatBots.project_id.in_(list(project_names.keys())), 

149 db.Tasks.object_type == self.OBJECT_TYPE, 

150 db.Tasks.company_id == ctx.company_id, 

151 ) 

152 

153 session = SessionController() 

154 database_names = { 

155 i['id']: i['name'] 

156 for i in session.database_controller.get_list() 

157 } 

158 

159 bots = [] 

160 for bot, task in query.all(): 

161 agent = self.agents_controller.get_agent_by_id(bot.agent_id) 

162 agent_obj = agent.as_dict() if agent is not None else None 

163 bots.append( 

164 { 

165 'id': bot.id, 

166 'name': bot.name, 

167 'project': project_names[bot.project_id], 

168 'agent': agent_obj, 

169 'database_id': bot.database_id, # TODO remove in future 

170 'database': database_names.get(bot.database_id, '?'), 

171 'model_name': bot.model_name, 

172 'params': bot.params, 

173 'created_at': bot.created_at, 

174 'is_running': task.active, 

175 'last_error': task.last_error, 

176 'webhook_token': bot.webhook_token, 

177 } 

178 ) 

179 

180 return bots 

181 

182 def add_chatbot( 

183 self, 

184 name: str, 

185 project_name: str, 

186 model_name: str = None, 

187 agent_name: str = None, 

188 database_id: int = None, 

189 is_running: bool = True, 

190 params: Dict[str, str] = {}) -> db.ChatBots: 

191 ''' 

192 Adds a chatbot to the database. 

193 

194 Parameters: 

195 name (str): The name of the new chatbot 

196 project_name (str): The containing project 

197 model_name (str): The name of the existing ML model the chatbot will use 

198 agent_name (str): The name of the existing agent the chatbot will use 

199 database_id (int): The ID of the existing database the chatbot will use 

200 is_running (bool): Whether or not to start the chatbot right after creation 

201 params: (Dict[str, str]): Parameters to use when running the chatbot 

202 

203 Returns: 

204 bot (db.ChatBots): The created chatbot 

205 ''' 

206 

207 is_cloud = config.get('cloud', False) 

208 if is_cloud and ctx.user_class == 0: 208 ↛ 209line 208 didn't jump to line 209 because the condition on line 208 was never true

209 raise Exception("You can't create chatbot") 

210 

211 if project_name is None: 211 ↛ 212line 211 didn't jump to line 212 because the condition on line 211 was never true

212 project_name = default_project 

213 project = self.project_controller.get(name=project_name) 

214 

215 bot = self.get_chatbot(name, project_name) 

216 

217 if bot is not None: 217 ↛ 218line 217 didn't jump to line 218 because the condition on line 217 was never true

218 raise Exception(f'Chat bot already exists: {name}') 

219 

220 # check database 

221 session_controller = SessionController() 

222 db_record = session_controller.integration_controller.get_by_id(database_id) 

223 if db_record is None: 223 ↛ 224line 223 didn't jump to line 224 because the condition on line 223 was never true

224 raise Exception(f"Database doesn't exist: {database_id}") 

225 

226 if model_name is None and agent_name is None: 226 ↛ 227line 226 didn't jump to line 227 because the condition on line 226 was never true

227 raise ValueError('Need to provide either "model_name" or "agent_name" when creating a chatbot') 

228 if agent_name is not None: 228 ↛ 229line 228 didn't jump to line 229 because the condition on line 228 was never true

229 agent = self.agents_controller.get_agent(agent_name, project_name) 

230 if agent is None: 

231 raise ValueError(f"Agent with name doesn't exist: {agent_name}") 

232 model_name = agent.model_name 

233 agent_id = agent.id 

234 else: 

235 # Create a new agent with the given model name. 

236 agent_id = None 

237 

238 bot = db.ChatBots( 

239 name=name, 

240 project_id=project.id, 

241 agent_id=agent_id, 

242 model_name=model_name, 

243 database_id=database_id, 

244 params=params, 

245 ) 

246 db.session.add(bot) 

247 db.session.flush() 

248 

249 task_record = db.Tasks( 

250 company_id=ctx.company_id, 

251 user_class=ctx.user_class, 

252 

253 object_type=self.OBJECT_TYPE, 

254 object_id=bot.id, 

255 active=is_running 

256 ) 

257 db.session.add(task_record) 

258 

259 db.session.commit() 

260 

261 return bot 

262 

263 def update_chatbot( 

264 self, 

265 chatbot_name: str, 

266 project_name: str = default_project, 

267 name: str = None, 

268 model_name: str = None, 

269 agent_name: str = None, 

270 database_id: int = None, 

271 is_running: bool = None, 

272 params: Dict[str, str] = None, 

273 webhook_token: str = None) -> db.ChatBots: 

274 ''' 

275 Updates a chatbot in the database, creating it if it doesn't already exist. 

276 

277 Parameters: 

278 chatbot_name (str): The name of the new chatbot, or existing chatbot to update 

279 project_name (str): The containing project 

280 name (str): The updated name of the chatbot 

281 model_name (str): The name of the existing ML model the chatbot will use 

282 agent_name (str): The name of the existing agent the chatbot will use 

283 database_id (int): The ID of the existing database the chatbot will use 

284 is_running (bool): Whether or not the chatbot will run after update/creation 

285 params: (Dict[str, str]): Parameters to use when running the chatbot 

286 

287 Returns: 

288 bot (db.ChatBots): The created or updated chatbot 

289 ''' 

290 

291 existing_chatbot = self.get_chatbot(chatbot_name, project_name=project_name) 

292 if existing_chatbot is None: 292 ↛ 293line 292 didn't jump to line 293 because the condition on line 292 was never true

293 raise Exception(f'Chat bot not found: {chatbot_name}') 

294 

295 existing_chatbot_rec = db.ChatBots.query.get(existing_chatbot['id']) 

296 

297 if name is not None and name != chatbot_name: 297 ↛ 299line 297 didn't jump to line 299 because the condition on line 297 was never true

298 # check new name 

299 bot2 = self.get_chatbot(name, project_name=project_name) 

300 if bot2 is not None: 

301 raise Exception(f'Chat already exists: {name}') 

302 

303 existing_chatbot_rec.name = name 

304 

305 if agent_name is not None: 305 ↛ 306line 305 didn't jump to line 306 because the condition on line 305 was never true

306 agent = self.agents_controller.get_agent(agent_name, project_name) 

307 if agent is None: 

308 raise ValueError(f"Agent with name doesn't exist: {agent_name}") 

309 existing_chatbot_rec.agent_id = agent.id 

310 

311 if model_name is not None: 311 ↛ 313line 311 didn't jump to line 313 because the condition on line 311 was never true

312 # TODO check model_name 

313 existing_chatbot_rec.model_name = model_name 

314 if database_id is not None: 314 ↛ 316line 314 didn't jump to line 316 because the condition on line 314 was never true

315 # TODO check database_id 

316 existing_chatbot_rec.database_id = database_id 

317 

318 task = db.Tasks.query.filter( 

319 db.Tasks.object_type == self.OBJECT_TYPE, 

320 db.Tasks.object_id == existing_chatbot_rec.id, 

321 db.Tasks.company_id == ctx.company_id, 

322 ).first() 

323 

324 if task is not None: 324 ↛ 331line 324 didn't jump to line 331 because the condition on line 324 was always true

325 if is_running is not None: 325 ↛ 326line 325 didn't jump to line 326 because the condition on line 325 was never true

326 task.active = is_running 

327 

328 # reload task 

329 task.reload = True 

330 

331 if params is not None: 331 ↛ 337line 331 didn't jump to line 337 because the condition on line 331 was always true

332 # Merge params on update 

333 existing_params = existing_chatbot_rec.params or {} 

334 params.update(existing_params) 

335 existing_chatbot_rec.params = params 

336 

337 if webhook_token is not None: 337 ↛ 338line 337 didn't jump to line 338 because the condition on line 337 was never true

338 existing_chatbot_rec.webhook_token = webhook_token 

339 

340 db.session.commit() 

341 

342 return existing_chatbot_rec 

343 

344 def delete_chatbot(self, chatbot_name: str, project_name: str = default_project): 

345 ''' 

346 Deletes a chatbot by name. 

347 

348 Parameters: 

349 chatbot_name (str): The name of the chatbot to delete 

350 project_name (str): The name of the containing project 

351 ''' 

352 

353 bot = self.get_chatbot(chatbot_name, project_name) 

354 if bot is None: 354 ↛ 355line 354 didn't jump to line 355 because the condition on line 354 was never true

355 raise Exception(f"Chat bot doesn't exist: {chatbot_name}") 

356 

357 bot_rec = db.ChatBots.query.get(bot['id']) 

358 

359 task = db.Tasks.query.filter( 

360 db.Tasks.object_type == self.OBJECT_TYPE, 

361 db.Tasks.object_id == bot_rec.id, 

362 db.Tasks.company_id == ctx.company_id, 

363 ).first() 

364 

365 if task is not None: 365 ↛ 368line 365 didn't jump to line 368 because the condition on line 365 was always true

366 db.session.delete(task) 

367 

368 db.session.delete(bot_rec) 

369 

370 db.session.commit() 

371 

372 def on_webhook(self, webhook_token: str, request: dict, chat_bot_memory: dict): 

373 """ 

374 Handles incoming webhook requests. 

375 Finds the chat bot associated with the webhook token and passes the request to the chat bot task. 

376 

377 Args: 

378 webhook_token (str): The token to uniquely identify the webhook. 

379 request (dict): The incoming webhook request. 

380 chat_bot_memory (dict): The memory of the various chat-bots mapped by their webhook tokens. 

381 """ 

382 query = db.session.query( 

383 db.ChatBots, db.Tasks 

384 ).join( 

385 db.Tasks, db.ChatBots.id == db.Tasks.object_id 

386 ).filter( 

387 db.ChatBots.webhook_token == webhook_token, 

388 db.Tasks.object_type == self.OBJECT_TYPE, 

389 db.Tasks.company_id == ctx.company_id, 

390 ) 

391 result = query.first() 

392 

393 chat_bot, task = result if result is not None else (None, None) 

394 

395 if chat_bot is None: 

396 raise Exception(f"No chat bot exists for webhook token: {webhook_token}") 

397 

398 if not task.active: 

399 raise Exception(f"Chat bot is not running: {chat_bot.name}") 

400 

401 chat_bot_task = ChatBotTask(task_id=task.id, object_id=chat_bot.id) 

402 

403 if webhook_token in chat_bot_memory: 

404 chat_bot_task.set_memory(chat_bot_memory[webhook_token]) 

405 else: 

406 chat_bot_memory[webhook_token] = chat_bot_task.get_memory() 

407 

408 chat_bot_task.on_webhook(request)