ambivo-agents 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,605 @@
1
+ # ambivo_agents/services/agent_service.py
2
+ """
3
+ Agent Service for managing agent sessions and message processing - UPDATED WITH YOUTUBE SUPPORT.
4
+ """
5
+
6
+ import asyncio
7
+ import uuid
8
+ import time
9
+ import logging
10
+ from typing import Dict, List, Any, Optional
11
+ from datetime import datetime, timedelta
12
+
13
+ from ..core.base import AgentRole, AgentMessage, MessageType, ExecutionContext
14
+ from ..core.memory import create_redis_memory_manager
15
+ from ..core.llm import create_multi_provider_llm_service
16
+ from ..config.loader import (
17
+ load_config,
18
+ get_config_section,
19
+ validate_agent_capabilities,
20
+ get_available_agent_types,
21
+ get_enabled_capabilities,
22
+ get_available_agent_type_names
23
+ )
24
+ from .factory import AgentFactory
25
+
26
+
27
+ class AgentSession:
28
+ """Manages a single agent session - UPDATED WITH YOUTUBE SUPPORT"""
29
+
30
+ def __init__(self, session_id: str, preferred_llm_provider: str = None):
31
+ # Load configuration from YAML
32
+ self.config = load_config()
33
+ self.session_id = session_id
34
+ self.redis_config = get_config_section('redis', self.config)
35
+ self.llm_config = get_config_section('llm', self.config)
36
+ self.service_config = self.config.get('service', {})
37
+
38
+ # Use centralized capability checking
39
+ self.capabilities = validate_agent_capabilities(self.config)
40
+ self.available_agent_types = get_available_agent_types(self.config)
41
+
42
+ self.preferred_llm_provider = preferred_llm_provider or self.llm_config.get('preferred_provider', 'openai')
43
+ self.agents = {}
44
+ self.proxy_agent = None
45
+ self.created_at = datetime.now()
46
+ self.last_activity = datetime.now()
47
+ self.message_count = 0
48
+
49
+ # Setup logging
50
+ log_level = self.service_config.get('log_level', 'INFO')
51
+ self.logger = logging.getLogger(f"AgentSession-{session_id[:8]}")
52
+ self.logger.setLevel(getattr(logging, log_level))
53
+
54
+ # Initialize LLM service
55
+ self._initialize_llm_service()
56
+
57
+ # Initialize agents for this session
58
+ self._initialize_agents()
59
+
60
+ def _initialize_llm_service(self):
61
+ """Initialize the LLM service"""
62
+ try:
63
+ self.llm_service = create_multi_provider_llm_service(
64
+ config_data=self.llm_config,
65
+ preferred_provider=self.preferred_llm_provider
66
+ )
67
+ self.logger.info(f"LLM service initialized with provider: {self.llm_service.get_current_provider()}")
68
+ except Exception as e:
69
+ self.logger.error(f"Failed to initialize LLM service: {e}")
70
+ raise e
71
+
72
+ def _initialize_agents(self):
73
+ """Initialize all agents based on configuration - UPDATED WITH YOUTUBE SUPPORT"""
74
+
75
+ # Core Assistant Agent
76
+ assistant_id = f"assistant_{self.session_id}"
77
+ assistant_memory = create_redis_memory_manager(assistant_id, self.redis_config)
78
+
79
+ self.agents['assistant'] = AgentFactory.create_agent(
80
+ role=AgentRole.ASSISTANT,
81
+ agent_id=assistant_id,
82
+ memory_manager=assistant_memory,
83
+ llm_service=self.llm_service,
84
+ config=self.config
85
+ )
86
+
87
+ # Code Executor Agent (if enabled)
88
+ if self.capabilities.get('code_execution', False):
89
+ executor_id = f"executor_{self.session_id}"
90
+ executor_memory = create_redis_memory_manager(executor_id, self.redis_config)
91
+
92
+ self.agents['executor'] = AgentFactory.create_agent(
93
+ role=AgentRole.CODE_EXECUTOR,
94
+ agent_id=executor_id,
95
+ memory_manager=executor_memory,
96
+ llm_service=self.llm_service,
97
+ config=self.config
98
+ )
99
+
100
+ # Create specialized agents based on enabled capabilities
101
+
102
+ # Web Search Agent (if enabled)
103
+ if self.capabilities.get('web_search', False):
104
+ search_id = f"websearch_{self.session_id}"
105
+ search_memory = create_redis_memory_manager(search_id, self.redis_config)
106
+
107
+ try:
108
+ from ..agents.web_search import WebSearchAgent
109
+ self.agents['web_search'] = WebSearchAgent(
110
+ agent_id=search_id,
111
+ memory_manager=search_memory,
112
+ llm_service=self.llm_service
113
+ )
114
+ self.logger.info("Created WebSearchAgent")
115
+ except Exception as e:
116
+ self.logger.error(f"Failed to create WebSearchAgent: {e}")
117
+
118
+ # Knowledge Base Agent (if enabled)
119
+ if self.capabilities.get('knowledge_base', False):
120
+ kb_id = f"knowledge_{self.session_id}"
121
+ kb_memory = create_redis_memory_manager(kb_id, self.redis_config)
122
+
123
+ try:
124
+ from ..agents.knowledge_base import KnowledgeBaseAgent
125
+ self.agents['knowledge_base'] = KnowledgeBaseAgent(
126
+ agent_id=kb_id,
127
+ memory_manager=kb_memory,
128
+ llm_service=self.llm_service
129
+ )
130
+ self.logger.info("Created KnowledgeBaseAgent")
131
+ except Exception as e:
132
+ self.logger.error(f"Failed to create KnowledgeBaseAgent: {e}")
133
+
134
+ # Web Scraper Agent (if enabled)
135
+ if self.capabilities.get('web_scraping', False):
136
+ scraper_id = f"webscraper_{self.session_id}"
137
+ scraper_memory = create_redis_memory_manager(scraper_id, self.redis_config)
138
+
139
+ try:
140
+ from ..agents.web_scraper import WebScraperAgent
141
+ self.agents['web_scraper'] = WebScraperAgent(
142
+ agent_id=scraper_id,
143
+ memory_manager=scraper_memory,
144
+ llm_service=self.llm_service
145
+ )
146
+ self.logger.info("Created WebScraperAgent")
147
+ except Exception as e:
148
+ self.logger.error(f"Failed to create WebScraperAgent: {e}")
149
+
150
+ # Media Editor Agent (if enabled)
151
+ if self.capabilities.get('media_editor', False):
152
+ media_id = f"mediaeditor_{self.session_id}"
153
+ media_memory = create_redis_memory_manager(media_id, self.redis_config)
154
+
155
+ try:
156
+ from ..agents.media_editor import MediaEditorAgent
157
+ self.agents['media_editor'] = MediaEditorAgent(
158
+ agent_id=media_id,
159
+ memory_manager=media_memory,
160
+ llm_service=self.llm_service
161
+ )
162
+ self.logger.info("Created MediaEditorAgent")
163
+ except Exception as e:
164
+ self.logger.error(f"Failed to create MediaEditorAgent: {e}")
165
+
166
+ # YouTube Download Agent (if enabled)
167
+ if self.capabilities.get('youtube_download', False):
168
+ youtube_id = f"youtube_{self.session_id}"
169
+ youtube_memory = create_redis_memory_manager(youtube_id, self.redis_config)
170
+
171
+ try:
172
+ from ..agents.youtube_download import YouTubeDownloadAgent
173
+ self.agents['youtube_download'] = YouTubeDownloadAgent(
174
+ agent_id=youtube_id,
175
+ memory_manager=youtube_memory,
176
+ llm_service=self.llm_service
177
+ )
178
+ self.logger.info("Created YouTubeDownloadAgent")
179
+ except Exception as e:
180
+ self.logger.error(f"Failed to create YouTubeDownloadAgent: {e}")
181
+
182
+ if self.capabilities.get('moderator', False):
183
+ moderator_id = f"moderator_{self.session_id}"
184
+ moderator_memory = create_redis_memory_manager(moderator_id, self.redis_config)
185
+
186
+ try:
187
+ from ..agents.moderator import ModeratorAgent
188
+ self.agents['moderator'] = ModeratorAgent(
189
+ agent_id=moderator_id,
190
+ memory_manager=moderator_memory,
191
+ llm_service=self.llm_service
192
+ )
193
+ self.logger.info("Created ModeratorAgent")
194
+ except Exception as e:
195
+ self.logger.error(f"Failed to create ModeratorAgent: {e}")
196
+
197
+ # Fallback: Create a general researcher agent if no specialized agents were created
198
+ specialized_agents = ['web_search', 'knowledge_base', 'web_scraper', 'media_editor', 'youtube_download']
199
+ if not any(key in self.agents for key in specialized_agents):
200
+ researcher_id = f"researcher_{self.session_id}"
201
+ researcher_memory = create_redis_memory_manager(researcher_id, self.redis_config)
202
+
203
+ self.agents['researcher'] = AgentFactory.create_agent(
204
+ role=AgentRole.RESEARCHER,
205
+ agent_id=researcher_id,
206
+ memory_manager=researcher_memory,
207
+ llm_service=self.llm_service,
208
+ config=self.config
209
+ )
210
+ self.logger.info("Created fallback researcher agent")
211
+
212
+ # Proxy Agent (if enabled)
213
+ if self.capabilities.get('proxy', True):
214
+ proxy_id = f"proxy_{self.session_id}"
215
+ proxy_memory = create_redis_memory_manager(proxy_id, self.redis_config)
216
+
217
+ self.proxy_agent = AgentFactory.create_agent(
218
+ role=AgentRole.PROXY,
219
+ agent_id=proxy_id,
220
+ memory_manager=proxy_memory,
221
+ llm_service=self.llm_service,
222
+ config=self.config
223
+ )
224
+
225
+ # Register all agents with proxy
226
+ for agent in self.agents.values():
227
+ self.proxy_agent.register_agent(agent)
228
+
229
+ self.agents['proxy'] = self.proxy_agent
230
+
231
+ enabled_capabilities = get_enabled_capabilities(self.config)
232
+ self.logger.info(f"Initialized session with capabilities: {enabled_capabilities}")
233
+
234
+ async def process_message(self,
235
+ message_content: str,
236
+ user_id: str,
237
+ tenant_id: str = "",
238
+ conversation_id: str = None,
239
+ metadata: Dict[str, Any] = None) -> AgentMessage:
240
+ """Process a user message through the agent system"""
241
+
242
+ self.last_activity = datetime.now()
243
+ self.message_count += 1
244
+
245
+ if not conversation_id:
246
+ conversation_id = str(uuid.uuid4())
247
+
248
+ # Create user message
249
+ user_message = AgentMessage(
250
+ id=str(uuid.uuid4()),
251
+ sender_id=f"user_{user_id}",
252
+ recipient_id=self.proxy_agent.agent_id if self.proxy_agent else list(self.agents.values())[0].agent_id,
253
+ content=message_content,
254
+ message_type=MessageType.USER_INPUT,
255
+ session_id=self.session_id,
256
+ conversation_id=conversation_id,
257
+ metadata=metadata or {}
258
+ )
259
+
260
+ # Create execution context
261
+ context = ExecutionContext(
262
+ session_id=self.session_id,
263
+ conversation_id=conversation_id,
264
+ user_id=user_id,
265
+ tenant_id=tenant_id,
266
+ metadata={
267
+ 'message_count': self.message_count,
268
+ 'session_age': (datetime.now() - self.created_at).total_seconds(),
269
+ 'enabled_capabilities': get_enabled_capabilities(self.config),
270
+ 'available_agent_types': get_available_agent_type_names(self.config),
271
+ 'config_source': 'agent_config.yaml',
272
+ **(metadata or {})
273
+ }
274
+ )
275
+
276
+ try:
277
+ # Process through proxy agent if available, otherwise use assistant
278
+ target_agent = self.proxy_agent or self.agents.get('assistant')
279
+
280
+ if not target_agent:
281
+ raise RuntimeError("No available agents to process message")
282
+
283
+ response = await target_agent.process_message(user_message, context)
284
+
285
+ # Add session metadata
286
+ response.metadata.update({
287
+ 'session_id': self.session_id,
288
+ 'message_count': self.message_count,
289
+ 'processing_time': (datetime.now() - self.last_activity).total_seconds(),
290
+ 'enabled_capabilities': get_enabled_capabilities(self.config),
291
+ 'available_agent_types': get_available_agent_type_names(self.config),
292
+ 'config_source': 'agent_config.yaml'
293
+ })
294
+
295
+ self.logger.debug(f"Processed message {self.message_count} in conversation {conversation_id}")
296
+ return response
297
+
298
+ except Exception as e:
299
+ self.logger.error(f"Error processing message: {e}")
300
+ error_response = AgentMessage(
301
+ id=str(uuid.uuid4()),
302
+ sender_id=target_agent.agent_id if 'target_agent' in locals() else "system",
303
+ recipient_id=user_message.sender_id,
304
+ content=f"I encountered an error processing your request: {str(e)}",
305
+ message_type=MessageType.ERROR,
306
+ session_id=self.session_id,
307
+ conversation_id=conversation_id,
308
+ metadata={'error': str(e), 'message_count': self.message_count, 'config_source': 'agent_config.yaml'}
309
+ )
310
+ return error_response
311
+
312
+ def get_session_stats(self) -> Dict[str, Any]:
313
+ """Get session statistics"""
314
+ return {
315
+ 'session_id': self.session_id,
316
+ 'created_at': self.created_at.isoformat(),
317
+ 'last_activity': self.last_activity.isoformat(),
318
+ 'message_count': self.message_count,
319
+ 'session_age_seconds': (datetime.now() - self.created_at).total_seconds(),
320
+ 'agent_count': len(self.agents),
321
+ 'available_agents': list(self.agents.keys()),
322
+ 'enabled_capabilities': get_enabled_capabilities(self.config),
323
+ 'available_agent_types': get_available_agent_type_names(self.config),
324
+ 'llm_provider': self.llm_service.get_current_provider() if self.llm_service else None,
325
+ 'config_source': 'agent_config.yaml',
326
+ 'redis_config': {
327
+ 'host': self.redis_config.get('host'),
328
+ 'port': self.redis_config.get('port'),
329
+ 'db': self.redis_config.get('db')
330
+ }
331
+ }
332
+
333
+
334
+ class AgentService:
335
+ """Agent Service for managing multiple agent sessions - UPDATED WITH YOUTUBE SUPPORT"""
336
+
337
+ def __init__(self, preferred_llm_provider: str = None):
338
+ """Initialize the Agent Service"""
339
+
340
+ # Load configuration from YAML
341
+ self.config = load_config()
342
+ self.redis_config = get_config_section('redis', self.config)
343
+ self.llm_config = get_config_section('llm', self.config)
344
+ self.service_config = self.config.get('service', {})
345
+
346
+ # Use centralized capability checking
347
+ self.capabilities = validate_agent_capabilities(self.config)
348
+ self.available_agent_types = get_available_agent_types(self.config)
349
+
350
+ self.preferred_llm_provider = preferred_llm_provider or self.llm_config.get('preferred_provider', 'openai')
351
+ self.sessions: Dict[str, AgentSession] = {}
352
+ self.session_timeout = self.service_config.get('session_timeout', 3600)
353
+ self.max_sessions = self.service_config.get('max_sessions', 100)
354
+
355
+ # Setup logging
356
+ self.logger = logging.getLogger("AgentService")
357
+ self._setup_logging()
358
+
359
+ # Performance tracking
360
+ self.total_messages_processed = 0
361
+ self.total_sessions_created = 0
362
+ self.start_time = datetime.now()
363
+
364
+ self.logger.info("Agent Service initialized from agent_config.yaml with YouTube support")
365
+
366
+ def _setup_logging(self):
367
+ """Setup logging configuration"""
368
+ log_level = self.service_config.get('log_level', 'INFO')
369
+ log_to_file = self.service_config.get('log_to_file', False)
370
+
371
+ handlers = [logging.StreamHandler()]
372
+ if log_to_file:
373
+ handlers.append(logging.FileHandler('agent_service.log'))
374
+
375
+ logging.basicConfig(
376
+ level=getattr(logging, log_level),
377
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
378
+ handlers=handlers
379
+ )
380
+
381
+ def create_session(self, session_id: str = None, preferred_llm_provider: str = None) -> str:
382
+ """Create a new agent session"""
383
+ if not session_id:
384
+ session_id = str(uuid.uuid4())
385
+
386
+ if session_id in self.sessions:
387
+ self.logger.warning(f"Session {session_id} already exists")
388
+ return session_id
389
+
390
+ # Check session limit
391
+ if len(self.sessions) >= self.max_sessions:
392
+ self.cleanup_expired_sessions()
393
+ if len(self.sessions) >= self.max_sessions:
394
+ oldest_session = min(self.sessions.values(), key=lambda s: s.last_activity)
395
+ self.delete_session(oldest_session.session_id)
396
+
397
+ try:
398
+ session = AgentSession(
399
+ session_id=session_id,
400
+ preferred_llm_provider=preferred_llm_provider or self.preferred_llm_provider
401
+ )
402
+
403
+ self.sessions[session_id] = session
404
+ self.total_sessions_created += 1
405
+
406
+ self.logger.info(f"Created session {session_id} (total: {len(self.sessions)})")
407
+ return session_id
408
+
409
+ except Exception as e:
410
+ self.logger.error(f"Failed to create session {session_id}: {e}")
411
+ raise
412
+
413
+ async def process_message(self,
414
+ message: str,
415
+ session_id: str,
416
+ user_id: str,
417
+ tenant_id: str = "",
418
+ conversation_id: str = None,
419
+ metadata: Dict[str, Any] = None) -> Dict[str, Any]:
420
+ """Process a user message through the agent system"""
421
+
422
+ start_time = time.time()
423
+
424
+ try:
425
+ # Get or create session
426
+ if session_id not in self.sessions:
427
+ self.create_session(session_id)
428
+
429
+ session = self.sessions[session_id]
430
+
431
+ # Process message
432
+ response = await session.process_message(
433
+ message_content=message,
434
+ user_id=user_id,
435
+ tenant_id=tenant_id,
436
+ conversation_id=conversation_id,
437
+ metadata=metadata
438
+ )
439
+
440
+ self.total_messages_processed += 1
441
+ processing_time = time.time() - start_time
442
+
443
+ return {
444
+ 'success': True,
445
+ 'response': response.content,
446
+ 'agent_id': response.sender_id,
447
+ 'message_id': response.id,
448
+ 'conversation_id': response.conversation_id,
449
+ 'session_id': session_id,
450
+ 'metadata': response.metadata,
451
+ 'timestamp': response.timestamp.isoformat(),
452
+ 'processing_time': processing_time,
453
+ 'message_count': session.message_count,
454
+ 'enabled_capabilities': get_enabled_capabilities(self.config),
455
+ 'available_agent_types': get_available_agent_type_names(self.config),
456
+ 'config_source': 'agent_config.yaml'
457
+ }
458
+
459
+ except Exception as e:
460
+ processing_time = time.time() - start_time
461
+ self.logger.error(f"Error processing message: {e}")
462
+
463
+ return {
464
+ 'success': False,
465
+ 'error': str(e),
466
+ 'session_id': session_id,
467
+ 'timestamp': datetime.now().isoformat(),
468
+ 'processing_time': processing_time,
469
+ 'config_source': 'agent_config.yaml'
470
+ }
471
+
472
+ def get_service_stats(self) -> Dict[str, Any]:
473
+ """Get comprehensive service statistics"""
474
+ current_time = datetime.now()
475
+ uptime = current_time - self.start_time
476
+
477
+ return {
478
+ 'service_status': 'healthy',
479
+ 'uptime_seconds': uptime.total_seconds(),
480
+ 'active_sessions': len(self.sessions),
481
+ 'total_sessions_created': self.total_sessions_created,
482
+ 'total_messages_processed': self.total_messages_processed,
483
+ 'enabled_capabilities': get_enabled_capabilities(self.config),
484
+ 'available_agent_types': self.available_agent_types,
485
+ 'available_agent_type_names': get_available_agent_type_names(self.config),
486
+ 'config_source': 'agent_config.yaml',
487
+ 'configuration_summary': {
488
+ 'redis_host': self.redis_config.get('host'),
489
+ 'redis_port': self.redis_config.get('port'),
490
+ 'llm_provider': self.preferred_llm_provider,
491
+ 'max_sessions': self.max_sessions,
492
+ 'session_timeout': self.session_timeout,
493
+ 'log_level': self.service_config.get('log_level', 'INFO')
494
+ },
495
+ 'timestamp': current_time.isoformat()
496
+ }
497
+
498
+ def health_check(self) -> dict[str, Any]:
499
+ """Comprehensive health check - UPDATED WITH YOUTUBE SUPPORT"""
500
+ health_status: dict[str, Any] = {
501
+ 'service_available': True,
502
+ 'timestamp': datetime.now().isoformat(),
503
+ 'config_source': 'agent_config.yaml'
504
+ }
505
+
506
+ try:
507
+ # Test Redis connectivity
508
+ test_memory = create_redis_memory_manager("health_check", self.redis_config)
509
+ health_status['redis_available'] = True
510
+
511
+ # Test LLM service
512
+ try:
513
+ test_llm = create_multi_provider_llm_service(self.llm_config, self.preferred_llm_provider)
514
+ health_status['llm_service_available'] = True
515
+ health_status['llm_current_provider'] = test_llm.get_current_provider()
516
+ health_status['llm_available_providers'] = test_llm.get_available_providers()
517
+ except Exception as e:
518
+ health_status['llm_service_available'] = False
519
+ health_status['llm_error'] = str(e)
520
+
521
+ # Agent capabilities using centralized checking
522
+ health_status['enabled_capabilities'] = get_enabled_capabilities(self.config)
523
+ health_status['available_agent_types'] = self.available_agent_types
524
+ health_status['available_agent_type_names'] = get_available_agent_type_names(self.config)
525
+
526
+ # Session health
527
+ health_status.update({
528
+ 'active_sessions': len(self.sessions),
529
+ 'max_sessions': self.max_sessions,
530
+ 'session_capacity_used': len(self.sessions) / self.max_sessions if self.max_sessions > 0 else 0
531
+ })
532
+
533
+ except Exception as e:
534
+ health_status.update({
535
+ 'service_available': False,
536
+ 'error': str(e),
537
+ 'overall_health': 'unhealthy'
538
+ })
539
+
540
+ return health_status
541
+
542
+ def cleanup_expired_sessions(self):
543
+ """Remove expired sessions"""
544
+ current_time = time.time()
545
+ expired_sessions = []
546
+
547
+ for session_id, session in self.sessions.items():
548
+ session_age = current_time - session.created_at.timestamp()
549
+ last_activity_age = current_time - session.last_activity.timestamp()
550
+
551
+ if (session_age > self.session_timeout or
552
+ last_activity_age > self.session_timeout):
553
+ expired_sessions.append(session_id)
554
+
555
+ for session_id in expired_sessions:
556
+ self.delete_session(session_id)
557
+
558
+ if expired_sessions:
559
+ self.logger.info(f"Cleaned up {len(expired_sessions)} expired sessions")
560
+
561
+ def delete_session(self, session_id: str) -> bool:
562
+ """Delete a session"""
563
+ if session_id in self.sessions:
564
+ try:
565
+ del self.sessions[session_id]
566
+ self.logger.info(f"Deleted session {session_id}")
567
+ return True
568
+ except Exception as e:
569
+ self.logger.error(f"Error deleting session {session_id}: {e}")
570
+ return False
571
+ return False
572
+
573
+ def get_session_info(self, session_id: str) -> Optional[Dict[str, Any]]:
574
+ """Get detailed information about a session"""
575
+ session = self.sessions.get(session_id)
576
+ if session:
577
+ return session.get_session_stats()
578
+ return None
579
+
580
+
581
+ def create_agent_service(preferred_llm_provider: str = None) -> AgentService:
582
+ """Create agent service using YAML configuration exclusively - UPDATED WITH YOUTUBE SUPPORT"""
583
+ return AgentService(preferred_llm_provider=preferred_llm_provider)
584
+
585
+
586
+ async def quick_chat(agent_service: AgentService,
587
+ message: str,
588
+ user_id: str,
589
+ session_id: str = None,
590
+ **kwargs) -> str:
591
+ """Quick chat interface"""
592
+ if not session_id:
593
+ session_id = str(uuid.uuid4())
594
+
595
+ result = await agent_service.process_message(
596
+ message=message,
597
+ session_id=session_id,
598
+ user_id=user_id,
599
+ **kwargs
600
+ )
601
+
602
+ if result['success']:
603
+ return result['response']
604
+ else:
605
+ return f"Error: {result['error']}"