Coverage for src/refinire/agents/notification.py: 87%

283 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-15 18:51 +0900

1""" 

2NotificationAgent implementation for sending notifications through various channels. 

3 

4NotificationAgentは様々なチャネルを通じて通知を送信するエージェントです。 

5メール、Webhook、ログなどの複数の通知方法をサポートしています。 

6""" 

7 

8import logging 

9import json 

10import smtplib 

11from abc import ABC, abstractmethod 

12from email.mime.text import MIMEText 

13from email.mime.multipart import MIMEMultipart 

14from typing import Any, List, Optional, Dict, Union 

15from pydantic import BaseModel, Field, field_validator 

16from datetime import datetime 

17import urllib.request 

18import urllib.parse 

19 

20from .flow.context import Context 

21from .flow.step import Step 

22 

23logger = logging.getLogger(__name__) 

24 

25 

26class NotificationChannel(ABC): 

27 """ 

28 Abstract base class for notification channels. 

29 通知チャネルの抽象基底クラス。 

30 """ 

31 

32 def __init__(self, name: str, enabled: bool = True): 

33 """ 

34 Initialize notification channel. 

35 通知チャネルを初期化します。 

36  

37 Args: 

38 name: Channel name / チャネル名 

39 enabled: Whether channel is enabled / チャネルが有効かどうか 

40 """ 

41 self.name = name 

42 self.enabled = enabled 

43 

44 @abstractmethod 

45 async def send(self, message: str, subject: str = None, context: Context = None) -> bool: 

46 """ 

47 Send notification through this channel. 

48 このチャネルを通じて通知を送信します。 

49  

50 Args: 

51 message: Notification message / 通知メッセージ 

52 subject: Message subject / メッセージ件名 

53 context: Execution context / 実行コンテキスト 

54  

55 Returns: 

56 bool: True if sent successfully / 送信が成功した場合True 

57 """ 

58 pass 

59 

60 

61class LogChannel(NotificationChannel): 

62 """ 

63 Notification channel that logs messages. 

64 メッセージをログに記録する通知チャネル。 

65 """ 

66 

67 def __init__(self, name: str = "log_channel", log_level: str = "INFO"): 

68 """ 

69 Initialize log channel. 

70 ログチャネルを初期化します。 

71  

72 Args: 

73 name: Channel name / チャネル名 

74 log_level: Log level (DEBUG, INFO, WARNING, ERROR) / ログレベル 

75 """ 

76 super().__init__(name) 

77 self.log_level = log_level.upper() 

78 

79 async def send(self, message: str, subject: str = None, context: Context = None) -> bool: 

80 """Send notification via logging.""" 

81 try: 

82 log_message = f"[NOTIFICATION] {subject}: {message}" if subject else f"[NOTIFICATION] {message}" 

83 

84 if self.log_level == "DEBUG": 

85 logger.debug(log_message) 

86 elif self.log_level == "INFO": 

87 logger.info(log_message) 

88 elif self.log_level == "WARNING": 

89 logger.warning(log_message) 

90 elif self.log_level == "ERROR": 

91 logger.error(log_message) 

92 else: 

93 logger.info(log_message) 

94 

95 return True 

96 except Exception as e: 

97 logger.error(f"Failed to send log notification: {e}") 

98 return False 

99 

100 

101class EmailChannel(NotificationChannel): 

102 """ 

103 Notification channel for email delivery. 

104 メール配信用の通知チャネル。 

105 """ 

106 

107 def __init__(self, name: str = "email_channel", smtp_server: str = None, 

108 smtp_port: int = 587, username: str = None, password: str = None, 

109 from_email: str = None, to_emails: List[str] = None, 

110 use_tls: bool = True): 

111 """ 

112 Initialize email channel. 

113 メールチャネルを初期化します。 

114  

115 Args: 

116 name: Channel name / チャネル名 

117 smtp_server: SMTP server address / SMTPサーバーアドレス 

118 smtp_port: SMTP server port / SMTPサーバーポート 

119 username: SMTP username / SMTPユーザー名 

120 password: SMTP password / SMTPパスワード 

121 from_email: Sender email address / 送信者メールアドレス 

122 to_emails: List of recipient email addresses / 受信者メールアドレスのリスト 

123 use_tls: Whether to use TLS encryption / TLS暗号化を使用するかどうか 

124 """ 

125 super().__init__(name) 

126 self.smtp_server = smtp_server 

127 self.smtp_port = smtp_port 

128 self.username = username 

129 self.password = password 

130 self.from_email = from_email 

131 self.to_emails = to_emails or [] 

132 self.use_tls = use_tls 

133 

134 async def send(self, message: str, subject: str = None, context: Context = None) -> bool: 

135 """Send notification via email.""" 

136 if not self.smtp_server or not self.from_email or not self.to_emails: 

137 logger.warning("Email channel not properly configured") 

138 return False 

139 

140 try: 

141 # Create message 

142 msg = MIMEMultipart() 

143 msg['From'] = self.from_email 

144 msg['To'] = ', '.join(self.to_emails) 

145 msg['Subject'] = subject or "Notification" 

146 

147 # Add message body 

148 msg.attach(MIMEText(message, 'plain')) 

149 

150 # Connect to server and send 

151 with smtplib.SMTP(self.smtp_server, self.smtp_port) as server: 

152 if self.use_tls: 

153 server.starttls() 

154 

155 if self.username and self.password: 

156 server.login(self.username, self.password) 

157 

158 server.send_message(msg) 

159 

160 logger.info(f"Email notification sent to {len(self.to_emails)} recipients") 

161 return True 

162 

163 except Exception as e: 

164 logger.error(f"Failed to send email notification: {e}") 

165 return False 

166 

167 

168class WebhookChannel(NotificationChannel): 

169 """ 

170 Notification channel for webhook delivery. 

171 Webhook配信用の通知チャネル。 

172 """ 

173 

174 def __init__(self, name: str = "webhook_channel", webhook_url: str = None, 

175 method: str = "POST", headers: Dict[str, str] = None, 

176 payload_template: str = None): 

177 """ 

178 Initialize webhook channel. 

179 Webhookチャネルを初期化します。 

180  

181 Args: 

182 name: Channel name / チャネル名 

183 webhook_url: Webhook URL / Webhook URL 

184 method: HTTP method (POST, PUT) / HTTPメソッド 

185 headers: Additional HTTP headers / 追加HTTPヘッダー 

186 payload_template: JSON payload template / JSONペイロードテンプレート 

187 """ 

188 super().__init__(name) 

189 self.webhook_url = webhook_url 

190 self.method = method.upper() 

191 self.headers = headers or {"Content-Type": "application/json"} 

192 self.payload_template = payload_template or '{{"message": "{message}", "subject": "{subject}", "timestamp": "{timestamp}"}}' 

193 

194 async def send(self, message: str, subject: str = None, context: Context = None) -> bool: 

195 """Send notification via webhook.""" 

196 if not self.webhook_url: 

197 logger.warning("Webhook URL not configured") 

198 return False 

199 

200 try: 

201 # Prepare payload 

202 timestamp = datetime.now().isoformat() 

203 

204 # Escape quotes in message and subject for JSON 

205 escaped_message = message.replace('"', '\\"').replace('\n', '\\n') 

206 escaped_subject = (subject or "").replace('"', '\\"').replace('\n', '\\n') 

207 

208 payload = self.payload_template.format( 

209 message=escaped_message, 

210 subject=escaped_subject, 

211 timestamp=timestamp 

212 ) 

213 

214 # Validate JSON format 

215 try: 

216 json.loads(payload) 

217 except json.JSONDecodeError as e: 

218 logger.error(f"Invalid JSON payload: {e}, payload: {payload}") 

219 return False 

220 

221 # Create request 

222 data = payload.encode('utf-8') 

223 req = urllib.request.Request( 

224 self.webhook_url, 

225 data=data, 

226 headers=self.headers, 

227 method=self.method 

228 ) 

229 

230 # Send request 

231 with urllib.request.urlopen(req, timeout=30) as response: 

232 if 200 <= response.status < 300: 

233 logger.info(f"Webhook notification sent successfully to {self.webhook_url}") 

234 return True 

235 else: 

236 logger.warning(f"Webhook responded with status {response.status}") 

237 return False 

238 

239 except Exception as e: 

240 logger.error(f"Failed to send webhook notification: {e}") 

241 return False 

242 

243 

244class SlackChannel(WebhookChannel): 

245 """ 

246 Specialized webhook channel for Slack notifications. 

247 Slack通知用の特化したWebhookチャネル。 

248 """ 

249 

250 def __init__(self, name: str = "slack_channel", webhook_url: str = None, 

251 channel: str = None, username: str = "NotificationBot"): 

252 """ 

253 Initialize Slack channel. 

254 Slackチャネルを初期化します。 

255  

256 Args: 

257 name: Channel name / チャネル名 

258 webhook_url: Slack webhook URL / Slack webhook URL 

259 channel: Slack channel name / Slackチャネル名 

260 username: Bot username / ボットユーザー名 

261 """ 

262 if channel: 

263 slack_payload = '{{"text": "{{message}}", "channel": "{}", "username": "{}"}}'.format(channel, username) 

264 else: 

265 slack_payload = '{{"text": "{message}"}}' 

266 

267 super().__init__( 

268 name=name, 

269 webhook_url=webhook_url, 

270 payload_template=slack_payload 

271 ) 

272 

273 

274class TeamsChannel(WebhookChannel): 

275 """ 

276 Specialized webhook channel for Microsoft Teams notifications. 

277 Microsoft Teams通知用の特化したWebhookチャネル。 

278 """ 

279 

280 def __init__(self, name: str = "teams_channel", webhook_url: str = None): 

281 """ 

282 Initialize Teams channel. 

283 Teamsチャネルを初期化します。 

284  

285 Args: 

286 name: Channel name / チャネル名 

287 webhook_url: Teams webhook URL / Teams webhook URL 

288 """ 

289 teams_payload = '''{{ 

290 "@type": "MessageCard", 

291 "@context": "http://schema.org/extensions", 

292 "themeColor": "0076D7", 

293 "summary": "{subject}", 

294 "sections": [{{ 

295 "activityTitle": "{subject}", 

296 "text": "{message}" 

297 }}] 

298 }}''' 

299 

300 super().__init__( 

301 name=name, 

302 webhook_url=webhook_url, 

303 payload_template=teams_payload 

304 ) 

305 

306 

307class FileChannel(NotificationChannel): 

308 """ 

309 Notification channel that writes to a file. 

310 ファイルに書き込む通知チャネル。 

311 """ 

312 

313 def __init__(self, name: str = "file_channel", file_path: str = None, 

314 append_mode: bool = True, include_timestamp: bool = True): 

315 """ 

316 Initialize file channel. 

317 ファイルチャネルを初期化します。 

318  

319 Args: 

320 name: Channel name / チャネル名 

321 file_path: Path to output file / 出力ファイルパス 

322 append_mode: Whether to append to file / ファイルに追記するかどうか 

323 include_timestamp: Whether to include timestamp / タイムスタンプを含めるかどうか 

324 """ 

325 super().__init__(name) 

326 self.file_path = file_path or "notifications.log" 

327 self.append_mode = append_mode 

328 self.include_timestamp = include_timestamp 

329 

330 async def send(self, message: str, subject: str = None, context: Context = None) -> bool: 

331 """Send notification to file.""" 

332 try: 

333 mode = "a" if self.append_mode else "w" 

334 

335 with open(self.file_path, mode, encoding='utf-8') as f: 

336 timestamp = datetime.now().isoformat() if self.include_timestamp else "" 

337 subject_part = f"[{subject}]" if subject else "" 

338 

339 if self.include_timestamp and subject: 

340 line = f"{timestamp} {subject_part} {message}\n" 

341 elif self.include_timestamp: 

342 line = f"{timestamp} {message}\n" 

343 elif subject: 

344 line = f"{subject_part} {message}\n" 

345 else: 

346 line = f"{message}\n" 

347 

348 f.write(line) 

349 

350 logger.info(f"File notification written to {self.file_path}") 

351 return True 

352 

353 except Exception as e: 

354 logger.error(f"Failed to write file notification: {e}") 

355 return False 

356 

357 

358class NotificationResult: 

359 """ 

360 Result of notification operation. 

361 通知操作の結果。 

362 """ 

363 

364 def __init__(self, total_channels: int = 0, successful_channels: int = 0, 

365 failed_channels: List[str] = None, errors: List[str] = None): 

366 """ 

367 Initialize notification result. 

368 通知結果を初期化します。 

369  

370 Args: 

371 total_channels: Total number of channels / 総チャネル数 

372 successful_channels: Number of successful channels / 成功したチャネル数 

373 failed_channels: List of failed channel names / 失敗したチャネル名のリスト 

374 errors: List of error messages / エラーメッセージのリスト 

375 """ 

376 self.total_channels = total_channels 

377 self.successful_channels = successful_channels 

378 self.failed_channels = failed_channels or [] 

379 self.errors = errors or [] 

380 self.timestamp = datetime.now() 

381 

382 @property 

383 def success_rate(self) -> float: 

384 """Get success rate as percentage.""" 

385 if self.total_channels == 0: 

386 return 100.0 

387 return (self.successful_channels / self.total_channels) * 100 

388 

389 @property 

390 def is_success(self) -> bool: 

391 """Check if all notifications were successful.""" 

392 return self.successful_channels == self.total_channels 

393 

394 def add_error(self, channel_name: str, error: str): 

395 """Add an error for a specific channel.""" 

396 self.failed_channels.append(channel_name) 

397 self.errors.append(f"[{channel_name}] {error}") 

398 

399 def __str__(self) -> str: 

400 return f"NotificationResult({self.successful_channels}/{self.total_channels} successful, {len(self.errors)} errors)" 

401 

402 

403class NotificationConfig(BaseModel): 

404 """ 

405 Configuration for NotificationAgent. 

406 NotificationAgentの設定。 

407 """ 

408 

409 name: str = Field(description="Name of the notification agent / 通知エージェントの名前") 

410 

411 channels: List[Dict[str, Any]] = Field( 

412 default=[], 

413 description="List of notification channel configurations / 通知チャネル設定のリスト" 

414 ) 

415 

416 default_subject: str = Field( 

417 default="Notification", 

418 description="Default subject for notifications / 通知のデフォルト件名" 

419 ) 

420 

421 fail_fast: bool = Field( 

422 default=False, 

423 description="Stop on first channel failure / 最初のチャネル失敗で停止" 

424 ) 

425 

426 store_result: bool = Field( 

427 default=True, 

428 description="Store notification result in context / 通知結果をコンテキストに保存" 

429 ) 

430 

431 require_all_success: bool = Field( 

432 default=False, 

433 description="Require all channels to succeed / 全てのチャネルの成功を要求" 

434 ) 

435 

436 @field_validator("channels") 

437 @classmethod 

438 def channels_not_empty(cls, v): 

439 """Validate that at least one channel is configured.""" 

440 if not v: 

441 logger.warning("No notification channels configured") 

442 return v 

443 

444 

445class NotificationAgent(Step): 

446 """ 

447 Notification agent for sending notifications through various channels. 

448 様々なチャネルを通じて通知を送信する通知エージェント。 

449  

450 The NotificationAgent supports multiple notification channels including 

451 email, webhooks, Slack, Teams, logging, and file output. 

452 NotificationAgentはメール、webhook、Slack、Teams、ログ、ファイル出力を含む 

453 複数の通知チャネルをサポートしています。 

454 """ 

455 

456 def __init__(self, config: NotificationConfig, custom_channels: List[NotificationChannel] = None): 

457 """ 

458 Initialize NotificationAgent. 

459 NotificationAgentを初期化します。 

460  

461 Args: 

462 config: Notification configuration / 通知設定 

463 custom_channels: Optional custom notification channels / オプションのカスタム通知チャネル 

464 """ 

465 super().__init__(name=config.name) 

466 self.config = config 

467 self.notification_channels = self._build_notification_channels(custom_channels or []) 

468 

469 def _build_notification_channels(self, custom_channels: List[NotificationChannel]) -> List[NotificationChannel]: 

470 """ 

471 Build notification channels from configuration and custom channels. 

472 設定とカスタムチャネルから通知チャネルを構築します。 

473 """ 

474 channels = list(custom_channels) 

475 

476 # Build channels from configuration 

477 # 設定からチャネルを構築 

478 for channel_config in self.config.channels: 

479 channel_type = channel_config.get("type") 

480 channel_name = channel_config.get("name", channel_type) 

481 enabled = channel_config.get("enabled", True) 

482 

483 if channel_type == "log": 

484 log_level = channel_config.get("log_level", "INFO") 

485 channels.append(LogChannel(channel_name, log_level)) 

486 

487 elif channel_type == "email": 

488 email_channel = EmailChannel( 

489 name=channel_name, 

490 smtp_server=channel_config.get("smtp_server"), 

491 smtp_port=channel_config.get("smtp_port", 587), 

492 username=channel_config.get("username"), 

493 password=channel_config.get("password"), 

494 from_email=channel_config.get("from_email"), 

495 to_emails=channel_config.get("to_emails", []), 

496 use_tls=channel_config.get("use_tls", True) 

497 ) 

498 email_channel.enabled = enabled 

499 channels.append(email_channel) 

500 

501 elif channel_type == "webhook": 

502 webhook_channel = WebhookChannel( 

503 name=channel_name, 

504 webhook_url=channel_config.get("webhook_url"), 

505 method=channel_config.get("method", "POST"), 

506 headers=channel_config.get("headers"), 

507 payload_template=channel_config.get("payload_template") 

508 ) 

509 webhook_channel.enabled = enabled 

510 channels.append(webhook_channel) 

511 

512 elif channel_type == "slack": 

513 slack_channel = SlackChannel( 

514 name=channel_name, 

515 webhook_url=channel_config.get("webhook_url"), 

516 channel=channel_config.get("channel"), 

517 username=channel_config.get("username", "NotificationBot") 

518 ) 

519 slack_channel.enabled = enabled 

520 channels.append(slack_channel) 

521 

522 elif channel_type == "teams": 

523 teams_channel = TeamsChannel( 

524 name=channel_name, 

525 webhook_url=channel_config.get("webhook_url") 

526 ) 

527 teams_channel.enabled = enabled 

528 channels.append(teams_channel) 

529 

530 elif channel_type == "file": 

531 file_channel = FileChannel( 

532 name=channel_name, 

533 file_path=channel_config.get("file_path"), 

534 append_mode=channel_config.get("append_mode", True), 

535 include_timestamp=channel_config.get("include_timestamp", True) 

536 ) 

537 file_channel.enabled = enabled 

538 channels.append(file_channel) 

539 

540 else: 

541 logger.warning(f"Unknown channel type: {channel_type}") 

542 

543 return channels 

544 

545 async def run(self, user_input: Optional[str], ctx: Context) -> Context: 

546 """ 

547 Execute the notification logic. 

548 通知ロジックを実行します。 

549  

550 Args: 

551 user_input: Notification message / 通知メッセージ 

552 ctx: Execution context / 実行コンテキスト 

553  

554 Returns: 

555 Context: Updated context with notification results / 通知結果を含む更新されたコンテキスト 

556 """ 

557 # Update step info 

558 # ステップ情報を更新 

559 ctx.update_step_info(self.name) 

560 

561 try: 

562 # Determine message to send 

563 # 送信するメッセージを決定 

564 message = user_input 

565 if message is None: 

566 message = ctx.get_user_input() 

567 

568 if not message: 

569 logger.warning(f"No message provided for notification in {self.name}") 

570 message = "Empty notification message" 

571 

572 # Get subject from context or use default 

573 # コンテキストから件名を取得するか、デフォルトを使用 

574 subject = ctx.shared_state.get(f"{self.name}_subject", self.config.default_subject) 

575 

576 # Send notifications 

577 # 通知を送信 

578 notification_result = await self._send_notifications(message, subject, ctx) 

579 

580 # Store result in context if requested 

581 # 要求された場合は結果をコンテキストに保存 

582 if self.config.store_result: 

583 ctx.shared_state[f"{self.name}_result"] = { 

584 "total_channels": notification_result.total_channels, 

585 "successful_channels": notification_result.successful_channels, 

586 "failed_channels": notification_result.failed_channels, 

587 "errors": notification_result.errors, 

588 "success_rate": notification_result.success_rate, 

589 "timestamp": notification_result.timestamp.isoformat() 

590 } 

591 

592 # Handle notification failure 

593 # 通知失敗を処理 

594 if self.config.require_all_success and not notification_result.is_success: 

595 error_summary = f"Notification failed: {len(notification_result.failed_channels)} channels failed" 

596 raise ValueError(error_summary) 

597 

598 if notification_result.is_success: 

599 logger.info(f"NotificationAgent '{self.name}': All notifications sent successfully") 

600 ctx.shared_state[f"{self.name}_status"] = "success" 

601 else: 

602 logger.warning(f"NotificationAgent '{self.name}': {notification_result.successful_channels}/{notification_result.total_channels} notifications sent") 

603 ctx.shared_state[f"{self.name}_status"] = "partial_success" 

604 

605 # Store individual channel results for easy access 

606 # 簡単なアクセスのために個別のチャネル結果を保存 

607 ctx.shared_state[f"{self.name}_success_count"] = notification_result.successful_channels 

608 ctx.shared_state[f"{self.name}_total_count"] = notification_result.total_channels 

609 

610 return ctx 

611 

612 except Exception as e: 

613 logger.error(f"NotificationAgent '{self.name}' error: {e}") 

614 

615 if self.config.store_result: 

616 ctx.shared_state[f"{self.name}_result"] = { 

617 "total_channels": 0, 

618 "successful_channels": 0, 

619 "failed_channels": [], 

620 "errors": [str(e)], 

621 "success_rate": 0.0, 

622 "timestamp": datetime.now().isoformat() 

623 } 

624 ctx.shared_state[f"{self.name}_status"] = "error" 

625 

626 if self.config.require_all_success: 

627 raise 

628 

629 return ctx 

630 

631 async def _send_notifications(self, message: str, subject: str, context: Context) -> NotificationResult: 

632 """ 

633 Send notifications through all configured channels. 

634 設定された全てのチャネルを通じて通知を送信します。 

635 """ 

636 enabled_channels = [ch for ch in self.notification_channels if ch.enabled] 

637 result = NotificationResult(total_channels=len(enabled_channels)) 

638 

639 for channel in enabled_channels: 

640 try: 

641 success = await channel.send(message, subject, context) 

642 

643 if success: 

644 result.successful_channels += 1 

645 logger.debug(f"Notification sent successfully via {channel.name}") 

646 else: 

647 result.add_error(channel.name, "Channel send method returned False") 

648 

649 if self.config.fail_fast: 

650 break 

651 

652 except Exception as e: 

653 error_message = f"Channel '{channel.name}' execution error: {e}" 

654 result.add_error(channel.name, error_message) 

655 logger.warning(error_message) 

656 

657 if self.config.fail_fast: 

658 break 

659 

660 return result 

661 

662 def add_channel(self, channel: NotificationChannel): 

663 """ 

664 Add a notification channel to the agent. 

665 エージェントに通知チャネルを追加します。 

666 """ 

667 self.notification_channels.append(channel) 

668 

669 def get_channels(self) -> List[NotificationChannel]: 

670 """ 

671 Get all notification channels. 

672 全ての通知チャネルを取得します。 

673 """ 

674 return self.notification_channels.copy() 

675 

676 def set_subject(self, subject: str, context: Context): 

677 """ 

678 Set notification subject in context for next notification. 

679 次の通知用にコンテキストで通知件名を設定します。 

680 """ 

681 context.shared_state[f"{self.name}_subject"] = subject 

682 

683 

684# Utility functions for creating common notification agents 

685# 一般的な通知エージェントを作成するためのユーティリティ関数 

686 

687def create_log_notifier(name: str = "log_notifier", log_level: str = "INFO") -> NotificationAgent: 

688 """ 

689 Create a notification agent that logs messages. 

690 メッセージをログに記録する通知エージェントを作成します。 

691 """ 

692 config = NotificationConfig( 

693 name=name, 

694 channels=[{"type": "log", "name": "log_channel", "log_level": log_level}] 

695 ) 

696 return NotificationAgent(config) 

697 

698 

699def create_file_notifier(name: str = "file_notifier", file_path: str = "notifications.log") -> NotificationAgent: 

700 """ 

701 Create a notification agent that writes to files. 

702 ファイルに書き込む通知エージェントを作成します。 

703 """ 

704 config = NotificationConfig( 

705 name=name, 

706 channels=[{"type": "file", "name": "file_channel", "file_path": file_path}] 

707 ) 

708 return NotificationAgent(config) 

709 

710 

711def create_webhook_notifier(name: str = "webhook_notifier", webhook_url: str = None) -> NotificationAgent: 

712 """ 

713 Create a notification agent that sends webhooks. 

714 Webhookを送信する通知エージェントを作成します。 

715 """ 

716 config = NotificationConfig( 

717 name=name, 

718 channels=[{"type": "webhook", "name": "webhook_channel", "webhook_url": webhook_url}] 

719 ) 

720 return NotificationAgent(config) 

721 

722 

723def create_slack_notifier(name: str = "slack_notifier", webhook_url: str = None, 

724 channel: str = None) -> NotificationAgent: 

725 """ 

726 Create a notification agent for Slack. 

727 Slack用の通知エージェントを作成します。 

728 """ 

729 config = NotificationConfig( 

730 name=name, 

731 channels=[{ 

732 "type": "slack", 

733 "name": "slack_channel", 

734 "webhook_url": webhook_url, 

735 "channel": channel 

736 }] 

737 ) 

738 return NotificationAgent(config) 

739 

740 

741def create_teams_notifier(name: str = "teams_notifier", webhook_url: str = None) -> NotificationAgent: 

742 """ 

743 Create a notification agent for Microsoft Teams. 

744 Microsoft Teams用の通知エージェントを作成します。 

745 """ 

746 config = NotificationConfig( 

747 name=name, 

748 channels=[{"type": "teams", "name": "teams_channel", "webhook_url": webhook_url}] 

749 ) 

750 return NotificationAgent(config) 

751 

752 

753def create_multi_channel_notifier(name: str = "multi_notifier", 

754 channels: List[Dict[str, Any]] = None) -> NotificationAgent: 

755 """ 

756 Create a notification agent with multiple channels. 

757 複数チャネルを持つ通知エージェントを作成します。 

758 """ 

759 config = NotificationConfig( 

760 name=name, 

761 channels=channels or [ 

762 {"type": "log", "name": "log", "log_level": "INFO"}, 

763 {"type": "file", "name": "file", "file_path": "notifications.log"} 

764 ] 

765 ) 

766 return NotificationAgent(config)