diff --git a/autogpt_platform/backend/backend/blocks/__init__.py b/autogpt_platform/backend/backend/blocks/__init__.py index d090aa41be01..1fd85aef4630 100644 --- a/autogpt_platform/backend/backend/blocks/__init__.py +++ b/autogpt_platform/backend/backend/blocks/__init__.py @@ -53,15 +53,22 @@ def all_subclasses(clz): if block.id in AVAILABLE_BLOCKS: raise ValueError(f"Block ID {block.name} error: {block.id} is already in use") + input_schema = block.input_schema.model_fields + output_schema = block.output_schema.model_fields + # Prevent duplicate field name in input_schema and output_schema - duplicate_field_names = set(block.input_schema.model_fields.keys()) & set( - block.output_schema.model_fields.keys() - ) + duplicate_field_names = set(input_schema.keys()) & set(output_schema.keys()) if duplicate_field_names: raise ValueError( f"{block.name} has duplicate field names in input_schema and output_schema: {duplicate_field_names}" ) + # Make sure `error` field is a string in the output schema + if "error" in output_schema and output_schema["error"].annotation is not str: + raise ValueError( + f"{block.name} `error` field in output_schema must be a string" + ) + for field in block.input_schema.model_fields.values(): if field.annotation is bool and field.default not in (True, False): raise ValueError(f"{block.name} has a boolean field with no default value") diff --git a/autogpt_platform/backend/backend/blocks/ai_shortform_video_block.py b/autogpt_platform/backend/backend/blocks/ai_shortform_video_block.py index f4f12c9fe157..127bb3ae8b4a 100644 --- a/autogpt_platform/backend/backend/blocks/ai_shortform_video_block.py +++ b/autogpt_platform/backend/backend/blocks/ai_shortform_video_block.py @@ -1,7 +1,6 @@ import logging import time from enum import Enum -from typing import Optional import requests from pydantic import Field @@ -156,7 +155,7 @@ class Input(BlockSchema): class Output(BlockSchema): video_url: str = Field(description="The URL of the created video") - error: Optional[str] = Field(description="Error message if the request failed") + error: str = Field(description="Error message if the request failed") def __init__(self): super().__init__( @@ -239,69 +238,58 @@ def wait_for_video( raise TimeoutError("Video creation timed out") def run(self, input_data: Input, **kwargs) -> BlockOutput: - try: - # Create a new Webhook.site URL - webhook_token, webhook_url = self.create_webhook() - logger.debug(f"Webhook URL: {webhook_url}") - - audio_url = input_data.background_music.audio_url - - payload = { - "frameRate": input_data.frame_rate, - "resolution": input_data.resolution, - "frameDurationMultiplier": 18, - "webhook": webhook_url, - "creationParams": { - "mediaType": input_data.video_style, - "captionPresetName": "Wrap 1", - "selectedVoice": input_data.voice.voice_id, - "hasEnhancedGeneration": True, - "generationPreset": input_data.generation_preset.name, - "selectedAudio": input_data.background_music, - "origin": "/create", - "inputText": input_data.script, - "flowType": "text-to-video", - "slug": "create-tiktok-video", - "hasToGenerateVoice": True, - "hasToTranscript": False, - "hasToSearchMedia": True, - "hasAvatar": False, - "hasWebsiteRecorder": False, - "hasTextSmallAtBottom": False, - "ratio": input_data.ratio, - "sourceType": "contentScraping", - "selectedStoryStyle": {"value": "custom", "label": "Custom"}, - "hasToGenerateVideos": input_data.video_style - != VisualMediaType.STOCK_VIDEOS, - "audioUrl": audio_url, - }, - } - - logger.debug("Creating video...") - response = self.create_video(input_data.api_key.get_secret_value(), payload) - pid = response.get("pid") - - if not pid: - logger.error( - f"Failed to create video: No project ID returned. API Response: {response}" - ) - yield "error", "Failed to create video: No project ID returned" - else: - logger.debug( - f"Video created with project ID: {pid}. Waiting for completion..." - ) - video_url = self.wait_for_video( - input_data.api_key.get_secret_value(), pid, webhook_token - ) - logger.debug(f"Video ready: {video_url}") - yield "video_url", video_url + # Create a new Webhook.site URL + webhook_token, webhook_url = self.create_webhook() + logger.debug(f"Webhook URL: {webhook_url}") + + audio_url = input_data.background_music.audio_url + + payload = { + "frameRate": input_data.frame_rate, + "resolution": input_data.resolution, + "frameDurationMultiplier": 18, + "webhook": webhook_url, + "creationParams": { + "mediaType": input_data.video_style, + "captionPresetName": "Wrap 1", + "selectedVoice": input_data.voice.voice_id, + "hasEnhancedGeneration": True, + "generationPreset": input_data.generation_preset.name, + "selectedAudio": input_data.background_music, + "origin": "/create", + "inputText": input_data.script, + "flowType": "text-to-video", + "slug": "create-tiktok-video", + "hasToGenerateVoice": True, + "hasToTranscript": False, + "hasToSearchMedia": True, + "hasAvatar": False, + "hasWebsiteRecorder": False, + "hasTextSmallAtBottom": False, + "ratio": input_data.ratio, + "sourceType": "contentScraping", + "selectedStoryStyle": {"value": "custom", "label": "Custom"}, + "hasToGenerateVideos": input_data.video_style + != VisualMediaType.STOCK_VIDEOS, + "audioUrl": audio_url, + }, + } - except requests.RequestException as e: - logger.exception("Error creating video") - yield "error", f"Error creating video: {str(e)}" - except ValueError as e: - logger.exception("Error in video creation process") - yield "error", str(e) - except TimeoutError as e: - logger.exception("Video creation timed out") - yield "error", str(e) + logger.debug("Creating video...") + response = self.create_video(input_data.api_key.get_secret_value(), payload) + pid = response.get("pid") + + if not pid: + logger.error( + f"Failed to create video: No project ID returned. API Response: {response}" + ) + raise RuntimeError("Failed to create video: No project ID returned") + else: + logger.debug( + f"Video created with project ID: {pid}. Waiting for completion..." + ) + video_url = self.wait_for_video( + input_data.api_key.get_secret_value(), pid, webhook_token + ) + logger.debug(f"Video ready: {video_url}") + yield "video_url", video_url diff --git a/autogpt_platform/backend/backend/blocks/basic.py b/autogpt_platform/backend/backend/blocks/basic.py index 095c3b0e92b8..60992e0f4591 100644 --- a/autogpt_platform/backend/backend/blocks/basic.py +++ b/autogpt_platform/backend/backend/blocks/basic.py @@ -330,20 +330,17 @@ def __init__(self): ) def run(self, input_data: Input, **kwargs) -> BlockOutput: - try: - # If no dictionary is provided, create a new one - if input_data.dictionary is None: - updated_dict = {} - else: - # Create a copy of the input dictionary to avoid modifying the original - updated_dict = input_data.dictionary.copy() + # If no dictionary is provided, create a new one + if input_data.dictionary is None: + updated_dict = {} + else: + # Create a copy of the input dictionary to avoid modifying the original + updated_dict = input_data.dictionary.copy() - # Add the new key-value pair - updated_dict[input_data.key] = input_data.value + # Add the new key-value pair + updated_dict[input_data.key] = input_data.value - yield "updated_dictionary", updated_dict - except Exception as e: - yield "error", f"Failed to add entry to dictionary: {str(e)}" + yield "updated_dictionary", updated_dict class AddToListBlock(Block): @@ -401,23 +398,20 @@ def __init__(self): ) def run(self, input_data: Input, **kwargs) -> BlockOutput: - try: - # If no list is provided, create a new one - if input_data.list is None: - updated_list = [] - else: - # Create a copy of the input list to avoid modifying the original - updated_list = input_data.list.copy() + # If no list is provided, create a new one + if input_data.list is None: + updated_list = [] + else: + # Create a copy of the input list to avoid modifying the original + updated_list = input_data.list.copy() - # Add the new entry - if input_data.position is None: - updated_list.append(input_data.entry) - else: - updated_list.insert(input_data.position, input_data.entry) + # Add the new entry + if input_data.position is None: + updated_list.append(input_data.entry) + else: + updated_list.insert(input_data.position, input_data.entry) - yield "updated_list", updated_list - except Exception as e: - yield "error", f"Failed to add entry to list: {str(e)}" + yield "updated_list", updated_list class NoteBlock(Block): diff --git a/autogpt_platform/backend/backend/blocks/block.py b/autogpt_platform/backend/backend/blocks/block.py index a4bea7aee794..a4bf8f6ac524 100644 --- a/autogpt_platform/backend/backend/blocks/block.py +++ b/autogpt_platform/backend/backend/blocks/block.py @@ -37,14 +37,12 @@ def run(self, input_data: Input, **kwargs) -> BlockOutput: if search := re.search(r"class (\w+)\(Block\):", code): class_name = search.group(1) else: - yield "error", "No class found in the code." - return + raise RuntimeError("No class found in the code.") if search := re.search(r"id=\"(\w+-\w+-\w+-\w+-\w+)\"", code): file_name = search.group(1) else: - yield "error", "No UUID found in the code." - return + raise RuntimeError("No UUID found in the code.") block_dir = os.path.dirname(__file__) file_path = f"{block_dir}/{file_name}.py" @@ -63,4 +61,4 @@ def run(self, input_data: Input, **kwargs) -> BlockOutput: yield "success", "Block installed successfully." except Exception as e: os.remove(file_path) - yield "error", f"[Code]\n{code}\n\n[Error]\n{str(e)}" + raise RuntimeError(f"[Code]\n{code}\n\n[Error]\n{str(e)}") diff --git a/autogpt_platform/backend/backend/blocks/decoder_block.py b/autogpt_platform/backend/backend/blocks/decoder_block.py index fb23ef2c563e..033cdfb0b355 100644 --- a/autogpt_platform/backend/backend/blocks/decoder_block.py +++ b/autogpt_platform/backend/backend/blocks/decoder_block.py @@ -35,8 +35,5 @@ def __init__(self): ) def run(self, input_data: Input, **kwargs) -> BlockOutput: - try: - decoded_text = codecs.decode(input_data.text, "unicode_escape") - yield "decoded_text", decoded_text - except Exception as e: - yield "error", f"Error decoding text: {str(e)}" + decoded_text = codecs.decode(input_data.text, "unicode_escape") + yield "decoded_text", decoded_text diff --git a/autogpt_platform/backend/backend/blocks/email_block.py b/autogpt_platform/backend/backend/blocks/email_block.py index 96e69e1ffca1..a7f0f82dcee7 100644 --- a/autogpt_platform/backend/backend/blocks/email_block.py +++ b/autogpt_platform/backend/backend/blocks/email_block.py @@ -67,35 +67,28 @@ def __init__(self): def send_email( creds: EmailCredentials, to_email: str, subject: str, body: str ) -> str: - try: - smtp_server = creds.smtp_server - smtp_port = creds.smtp_port - smtp_username = creds.smtp_username.get_secret_value() - smtp_password = creds.smtp_password.get_secret_value() + smtp_server = creds.smtp_server + smtp_port = creds.smtp_port + smtp_username = creds.smtp_username.get_secret_value() + smtp_password = creds.smtp_password.get_secret_value() - msg = MIMEMultipart() - msg["From"] = smtp_username - msg["To"] = to_email - msg["Subject"] = subject - msg.attach(MIMEText(body, "plain")) + msg = MIMEMultipart() + msg["From"] = smtp_username + msg["To"] = to_email + msg["Subject"] = subject + msg.attach(MIMEText(body, "plain")) - with smtplib.SMTP(smtp_server, smtp_port) as server: - server.starttls() - server.login(smtp_username, smtp_password) - server.sendmail(smtp_username, to_email, msg.as_string()) + with smtplib.SMTP(smtp_server, smtp_port) as server: + server.starttls() + server.login(smtp_username, smtp_password) + server.sendmail(smtp_username, to_email, msg.as_string()) - return "Email sent successfully" - except Exception as e: - return f"Failed to send email: {str(e)}" + return "Email sent successfully" def run(self, input_data: Input, **kwargs) -> BlockOutput: - status = self.send_email( + yield "status", self.send_email( input_data.creds, input_data.to_email, input_data.subject, input_data.body, ) - if "successfully" in status: - yield "status", status - else: - yield "error", status diff --git a/autogpt_platform/backend/backend/blocks/github/issues.py b/autogpt_platform/backend/backend/blocks/github/issues.py index 0ddeb547eee8..ee9391545d67 100644 --- a/autogpt_platform/backend/backend/blocks/github/issues.py +++ b/autogpt_platform/backend/backend/blocks/github/issues.py @@ -93,16 +93,13 @@ def run( credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - try: - id, url = self.post_comment( - credentials, - input_data.issue_url, - input_data.comment, - ) - yield "id", id - yield "url", url - except Exception as e: - yield "error", f"Failed to post comment: {str(e)}" + id, url = self.post_comment( + credentials, + input_data.issue_url, + input_data.comment, + ) + yield "id", id + yield "url", url # --8<-- [end:GithubCommentBlockExample] @@ -179,17 +176,14 @@ def run( credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - try: - number, url = self.create_issue( - credentials, - input_data.repo_url, - input_data.title, - input_data.body, - ) - yield "number", number - yield "url", url - except Exception as e: - yield "error", f"Failed to create issue: {str(e)}" + number, url = self.create_issue( + credentials, + input_data.repo_url, + input_data.title, + input_data.body, + ) + yield "number", number + yield "url", url class GithubReadIssueBlock(Block): @@ -262,16 +256,13 @@ def run( credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - try: - title, body, user = self.read_issue( - credentials, - input_data.issue_url, - ) - yield "title", title - yield "body", body - yield "user", user - except Exception as e: - yield "error", f"Failed to read issue: {str(e)}" + title, body, user = self.read_issue( + credentials, + input_data.issue_url, + ) + yield "title", title + yield "body", body + yield "user", user class GithubListIssuesBlock(Block): @@ -350,14 +341,11 @@ def run( credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - try: - issues = self.list_issues( - credentials, - input_data.repo_url, - ) - yield from (("issue", issue) for issue in issues) - except Exception as e: - yield "error", f"Failed to list issues: {str(e)}" + issues = self.list_issues( + credentials, + input_data.repo_url, + ) + yield from (("issue", issue) for issue in issues) class GithubAddLabelBlock(Block): @@ -428,15 +416,12 @@ def run( credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - try: - status = self.add_label( - credentials, - input_data.issue_url, - input_data.label, - ) - yield "status", status - except Exception as e: - yield "error", f"Failed to add label: {str(e)}" + status = self.add_label( + credentials, + input_data.issue_url, + input_data.label, + ) + yield "status", status class GithubRemoveLabelBlock(Block): @@ -512,15 +497,12 @@ def run( credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - try: - status = self.remove_label( - credentials, - input_data.issue_url, - input_data.label, - ) - yield "status", status - except Exception as e: - yield "error", f"Failed to remove label: {str(e)}" + status = self.remove_label( + credentials, + input_data.issue_url, + input_data.label, + ) + yield "status", status class GithubAssignIssueBlock(Block): @@ -594,15 +576,12 @@ def run( credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - try: - status = self.assign_issue( - credentials, - input_data.issue_url, - input_data.assignee, - ) - yield "status", status - except Exception as e: - yield "error", f"Failed to assign issue: {str(e)}" + status = self.assign_issue( + credentials, + input_data.issue_url, + input_data.assignee, + ) + yield "status", status class GithubUnassignIssueBlock(Block): @@ -676,12 +655,9 @@ def run( credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - try: - status = self.unassign_issue( - credentials, - input_data.issue_url, - input_data.assignee, - ) - yield "status", status - except Exception as e: - yield "error", f"Failed to unassign issue: {str(e)}" + status = self.unassign_issue( + credentials, + input_data.issue_url, + input_data.assignee, + ) + yield "status", status diff --git a/autogpt_platform/backend/backend/blocks/github/pull_requests.py b/autogpt_platform/backend/backend/blocks/github/pull_requests.py index 87540b66df59..b04c730dc375 100644 --- a/autogpt_platform/backend/backend/blocks/github/pull_requests.py +++ b/autogpt_platform/backend/backend/blocks/github/pull_requests.py @@ -87,14 +87,11 @@ def run( credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - try: - pull_requests = self.list_prs( - credentials, - input_data.repo_url, - ) - yield from (("pull_request", pr) for pr in pull_requests) - except Exception as e: - yield "error", f"Failed to list pull requests: {str(e)}" + pull_requests = self.list_prs( + credentials, + input_data.repo_url, + ) + yield from (("pull_request", pr) for pr in pull_requests) class GithubMakePullRequestBlock(Block): @@ -203,9 +200,7 @@ def run( error_message = error_details.get("message", "Unknown error") else: error_message = str(http_err) - yield "error", f"Failed to create pull request: {error_message}" - except Exception as e: - yield "error", f"Failed to create pull request: {str(e)}" + raise RuntimeError(f"Failed to create pull request: {error_message}") class GithubReadPullRequestBlock(Block): @@ -313,23 +308,20 @@ def run( credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - try: - title, body, author = self.read_pr( + title, body, author = self.read_pr( + credentials, + input_data.pr_url, + ) + yield "title", title + yield "body", body + yield "author", author + + if input_data.include_pr_changes: + changes = self.read_pr_changes( credentials, input_data.pr_url, ) - yield "title", title - yield "body", body - yield "author", author - - if input_data.include_pr_changes: - changes = self.read_pr_changes( - credentials, - input_data.pr_url, - ) - yield "changes", changes - except Exception as e: - yield "error", f"Failed to read pull request: {str(e)}" + yield "changes", changes class GithubAssignPRReviewerBlock(Block): @@ -418,9 +410,7 @@ def run( ) else: error_msg = f"HTTP error: {http_err} - {http_err.response.text}" - yield "error", error_msg - except Exception as e: - yield "error", f"Failed to assign reviewer: {str(e)}" + raise RuntimeError(error_msg) class GithubUnassignPRReviewerBlock(Block): @@ -490,15 +480,12 @@ def run( credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - try: - status = self.unassign_reviewer( - credentials, - input_data.pr_url, - input_data.reviewer, - ) - yield "status", status - except Exception as e: - yield "error", f"Failed to unassign reviewer: {str(e)}" + status = self.unassign_reviewer( + credentials, + input_data.pr_url, + input_data.reviewer, + ) + yield "status", status class GithubListPRReviewersBlock(Block): @@ -586,11 +573,8 @@ def run( credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - try: - reviewers = self.list_reviewers( - credentials, - input_data.pr_url, - ) - yield from (("reviewer", reviewer) for reviewer in reviewers) - except Exception as e: - yield "error", f"Failed to list reviewers: {str(e)}" + reviewers = self.list_reviewers( + credentials, + input_data.pr_url, + ) + yield from (("reviewer", reviewer) for reviewer in reviewers) diff --git a/autogpt_platform/backend/backend/blocks/github/repo.py b/autogpt_platform/backend/backend/blocks/github/repo.py index 63dcc7e1a143..29eeb757e21c 100644 --- a/autogpt_platform/backend/backend/blocks/github/repo.py +++ b/autogpt_platform/backend/backend/blocks/github/repo.py @@ -96,14 +96,11 @@ def run( credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - try: - tags = self.list_tags( - credentials, - input_data.repo_url, - ) - yield from (("tag", tag) for tag in tags) - except Exception as e: - yield "error", f"Failed to list tags: {str(e)}" + tags = self.list_tags( + credentials, + input_data.repo_url, + ) + yield from (("tag", tag) for tag in tags) class GithubListBranchesBlock(Block): @@ -183,14 +180,11 @@ def run( credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - try: - branches = self.list_branches( - credentials, - input_data.repo_url, - ) - yield from (("branch", branch) for branch in branches) - except Exception as e: - yield "error", f"Failed to list branches: {str(e)}" + branches = self.list_branches( + credentials, + input_data.repo_url, + ) + yield from (("branch", branch) for branch in branches) class GithubListDiscussionsBlock(Block): @@ -294,13 +288,10 @@ def run( credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - try: - discussions = self.list_discussions( - credentials, input_data.repo_url, input_data.num_discussions - ) - yield from (("discussion", discussion) for discussion in discussions) - except Exception as e: - yield "error", f"Failed to list discussions: {str(e)}" + discussions = self.list_discussions( + credentials, input_data.repo_url, input_data.num_discussions + ) + yield from (("discussion", discussion) for discussion in discussions) class GithubListReleasesBlock(Block): @@ -381,14 +372,11 @@ def run( credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - try: - releases = self.list_releases( - credentials, - input_data.repo_url, - ) - yield from (("release", release) for release in releases) - except Exception as e: - yield "error", f"Failed to list releases: {str(e)}" + releases = self.list_releases( + credentials, + input_data.repo_url, + ) + yield from (("release", release) for release in releases) class GithubReadFileBlock(Block): @@ -474,18 +462,15 @@ def run( credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - try: - raw_content, size = self.read_file( - credentials, - input_data.repo_url, - input_data.file_path.lstrip("/"), - input_data.branch, - ) - yield "raw_content", raw_content - yield "text_content", base64.b64decode(raw_content).decode("utf-8") - yield "size", size - except Exception as e: - yield "error", f"Failed to read file: {str(e)}" + raw_content, size = self.read_file( + credentials, + input_data.repo_url, + input_data.file_path.lstrip("/"), + input_data.branch, + ) + yield "raw_content", raw_content + yield "text_content", base64.b64decode(raw_content).decode("utf-8") + yield "size", size class GithubReadFolderBlock(Block): @@ -612,17 +597,14 @@ def run( credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - try: - files, dirs = self.read_folder( - credentials, - input_data.repo_url, - input_data.folder_path.lstrip("/"), - input_data.branch, - ) - yield from (("file", file) for file in files) - yield from (("dir", dir) for dir in dirs) - except Exception as e: - yield "error", f"Failed to read folder: {str(e)}" + files, dirs = self.read_folder( + credentials, + input_data.repo_url, + input_data.folder_path.lstrip("/"), + input_data.branch, + ) + yield from (("file", file) for file in files) + yield from (("dir", dir) for dir in dirs) class GithubMakeBranchBlock(Block): @@ -703,16 +685,13 @@ def run( credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - try: - status = self.create_branch( - credentials, - input_data.repo_url, - input_data.new_branch, - input_data.source_branch, - ) - yield "status", status - except Exception as e: - yield "error", f"Failed to create branch: {str(e)}" + status = self.create_branch( + credentials, + input_data.repo_url, + input_data.new_branch, + input_data.source_branch, + ) + yield "status", status class GithubDeleteBranchBlock(Block): @@ -775,12 +754,9 @@ def run( credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - try: - status = self.delete_branch( - credentials, - input_data.repo_url, - input_data.branch, - ) - yield "status", status - except Exception as e: - yield "error", f"Failed to delete branch: {str(e)}" + status = self.delete_branch( + credentials, + input_data.repo_url, + input_data.branch, + ) + yield "status", status diff --git a/autogpt_platform/backend/backend/blocks/google/gmail.py b/autogpt_platform/backend/backend/blocks/google/gmail.py index 9cecac81f133..beb96f343904 100644 --- a/autogpt_platform/backend/backend/blocks/google/gmail.py +++ b/autogpt_platform/backend/backend/blocks/google/gmail.py @@ -104,16 +104,11 @@ def __init__(self): def run( self, input_data: Input, *, credentials: GoogleCredentials, **kwargs ) -> BlockOutput: - try: - service = self._build_service(credentials, **kwargs) - messages = self._read_emails( - service, input_data.query, input_data.max_results - ) - for email in messages: - yield "email", email - yield "emails", messages - except Exception as e: - yield "error", str(e) + service = self._build_service(credentials, **kwargs) + messages = self._read_emails(service, input_data.query, input_data.max_results) + for email in messages: + yield "email", email + yield "emails", messages @staticmethod def _build_service(credentials: GoogleCredentials, **kwargs): @@ -267,14 +262,11 @@ def __init__(self): def run( self, input_data: Input, *, credentials: GoogleCredentials, **kwargs ) -> BlockOutput: - try: - service = GmailReadBlock._build_service(credentials, **kwargs) - send_result = self._send_email( - service, input_data.to, input_data.subject, input_data.body - ) - yield "result", send_result - except Exception as e: - yield "error", str(e) + service = GmailReadBlock._build_service(credentials, **kwargs) + send_result = self._send_email( + service, input_data.to, input_data.subject, input_data.body + ) + yield "result", send_result def _send_email(self, service, to: str, subject: str, body: str) -> dict: if not to or not subject or not body: @@ -342,12 +334,9 @@ def __init__(self): def run( self, input_data: Input, *, credentials: GoogleCredentials, **kwargs ) -> BlockOutput: - try: - service = GmailReadBlock._build_service(credentials, **kwargs) - labels = self._list_labels(service) - yield "result", labels - except Exception as e: - yield "error", str(e) + service = GmailReadBlock._build_service(credentials, **kwargs) + labels = self._list_labels(service) + yield "result", labels def _list_labels(self, service) -> list[dict]: results = service.users().labels().list(userId="me").execute() @@ -406,14 +395,9 @@ def __init__(self): def run( self, input_data: Input, *, credentials: GoogleCredentials, **kwargs ) -> BlockOutput: - try: - service = GmailReadBlock._build_service(credentials, **kwargs) - result = self._add_label( - service, input_data.message_id, input_data.label_name - ) - yield "result", result - except Exception as e: - yield "error", str(e) + service = GmailReadBlock._build_service(credentials, **kwargs) + result = self._add_label(service, input_data.message_id, input_data.label_name) + yield "result", result def _add_label(self, service, message_id: str, label_name: str) -> dict: label_id = self._get_or_create_label(service, label_name) @@ -494,14 +478,11 @@ def __init__(self): def run( self, input_data: Input, *, credentials: GoogleCredentials, **kwargs ) -> BlockOutput: - try: - service = GmailReadBlock._build_service(credentials, **kwargs) - result = self._remove_label( - service, input_data.message_id, input_data.label_name - ) - yield "result", result - except Exception as e: - yield "error", str(e) + service = GmailReadBlock._build_service(credentials, **kwargs) + result = self._remove_label( + service, input_data.message_id, input_data.label_name + ) + yield "result", result def _remove_label(self, service, message_id: str, label_name: str) -> dict: label_id = self._get_label_id(service, label_name) diff --git a/autogpt_platform/backend/backend/blocks/google/sheets.py b/autogpt_platform/backend/backend/blocks/google/sheets.py index e4318225cce1..e7878ff4b606 100644 --- a/autogpt_platform/backend/backend/blocks/google/sheets.py +++ b/autogpt_platform/backend/backend/blocks/google/sheets.py @@ -68,14 +68,9 @@ def __init__(self): def run( self, input_data: Input, *, credentials: GoogleCredentials, **kwargs ) -> BlockOutput: - try: - service = self._build_service(credentials, **kwargs) - data = self._read_sheet( - service, input_data.spreadsheet_id, input_data.range - ) - yield "result", data - except Exception as e: - yield "error", str(e) + service = self._build_service(credentials, **kwargs) + data = self._read_sheet(service, input_data.spreadsheet_id, input_data.range) + yield "result", data @staticmethod def _build_service(credentials: GoogleCredentials, **kwargs): @@ -162,17 +157,14 @@ def __init__(self): def run( self, input_data: Input, *, credentials: GoogleCredentials, **kwargs ) -> BlockOutput: - try: - service = GoogleSheetsReadBlock._build_service(credentials, **kwargs) - result = self._write_sheet( - service, - input_data.spreadsheet_id, - input_data.range, - input_data.values, - ) - yield "result", result - except Exception as e: - yield "error", str(e) + service = GoogleSheetsReadBlock._build_service(credentials, **kwargs) + result = self._write_sheet( + service, + input_data.spreadsheet_id, + input_data.range, + input_data.values, + ) + yield "result", result def _write_sheet( self, service, spreadsheet_id: str, range: str, values: list[list[str]] diff --git a/autogpt_platform/backend/backend/blocks/google_maps.py b/autogpt_platform/backend/backend/blocks/google_maps.py index 4edd74113dc2..3be57b93e818 100644 --- a/autogpt_platform/backend/backend/blocks/google_maps.py +++ b/autogpt_platform/backend/backend/blocks/google_maps.py @@ -82,17 +82,14 @@ def __init__(self): ) def run(self, input_data: Input, **kwargs) -> BlockOutput: - try: - places = self.search_places( - input_data.api_key.get_secret_value(), - input_data.query, - input_data.radius, - input_data.max_results, - ) - for place in places: - yield "place", place - except Exception as e: - yield "error", str(e) + places = self.search_places( + input_data.api_key.get_secret_value(), + input_data.query, + input_data.radius, + input_data.max_results, + ) + for place in places: + yield "place", place def search_places(self, api_key, query, radius, max_results): client = googlemaps.Client(key=api_key) diff --git a/autogpt_platform/backend/backend/blocks/ideogram.py b/autogpt_platform/backend/backend/blocks/ideogram.py index 0bf20ee6bdca..66dd22061447 100644 --- a/autogpt_platform/backend/backend/blocks/ideogram.py +++ b/autogpt_platform/backend/backend/blocks/ideogram.py @@ -128,9 +128,7 @@ class Input(BlockSchema): class Output(BlockSchema): result: str = SchemaField(description="Generated image URL") - error: Optional[str] = SchemaField( - description="Error message if the model run failed" - ) + error: str = SchemaField(description="Error message if the model run failed") def __init__(self): super().__init__( @@ -166,30 +164,27 @@ def __init__(self): def run(self, input_data: Input, **kwargs) -> BlockOutput: seed = input_data.seed - try: - # Step 1: Generate the image - result = self.run_model( + # Step 1: Generate the image + result = self.run_model( + api_key=input_data.api_key.get_secret_value(), + model_name=input_data.ideogram_model_name.value, + prompt=input_data.prompt, + seed=seed, + aspect_ratio=input_data.aspect_ratio.value, + magic_prompt_option=input_data.magic_prompt_option.value, + style_type=input_data.style_type.value, + negative_prompt=input_data.negative_prompt, + color_palette_name=input_data.color_palette_name.value, + ) + + # Step 2: Upscale the image if requested + if input_data.upscale == UpscaleOption.AI_UPSCALE: + result = self.upscale_image( api_key=input_data.api_key.get_secret_value(), - model_name=input_data.ideogram_model_name.value, - prompt=input_data.prompt, - seed=seed, - aspect_ratio=input_data.aspect_ratio.value, - magic_prompt_option=input_data.magic_prompt_option.value, - style_type=input_data.style_type.value, - negative_prompt=input_data.negative_prompt, - color_palette_name=input_data.color_palette_name.value, + image_url=result, ) - # Step 2: Upscale the image if requested - if input_data.upscale == UpscaleOption.AI_UPSCALE: - result = self.upscale_image( - api_key=input_data.api_key.get_secret_value(), - image_url=result, - ) - - yield "result", result - except Exception as e: - yield "error", str(e) + yield "result", result def run_model( self, diff --git a/autogpt_platform/backend/backend/blocks/llm.py b/autogpt_platform/backend/backend/blocks/llm.py index 15f75ecc7409..f38b5f5da72d 100644 --- a/autogpt_platform/backend/backend/blocks/llm.py +++ b/autogpt_platform/backend/backend/blocks/llm.py @@ -344,7 +344,7 @@ def parse_response(resp: str) -> tuple[dict[str, Any], str | None]: logger.error(f"Error calling LLM: {e}") retry_prompt = f"Error calling LLM: {e}" - yield "error", retry_prompt + raise RuntimeError(retry_prompt) class AITextGeneratorBlock(Block): @@ -390,14 +390,11 @@ def llm_call(input_data: AIStructuredResponseGeneratorBlock.Input) -> str: raise ValueError("Failed to get a response from the LLM.") def run(self, input_data: Input, **kwargs) -> BlockOutput: - try: - object_input_data = AIStructuredResponseGeneratorBlock.Input( - **{attr: getattr(input_data, attr) for attr in input_data.model_fields}, - expected_format={}, - ) - yield "response", self.llm_call(object_input_data) - except Exception as e: - yield "error", str(e) + object_input_data = AIStructuredResponseGeneratorBlock.Input( + **{attr: getattr(input_data, attr) for attr in input_data.model_fields}, + expected_format={}, + ) + yield "response", self.llm_call(object_input_data) class SummaryStyle(Enum): @@ -445,11 +442,8 @@ def __init__(self): ) def run(self, input_data: Input, **kwargs) -> BlockOutput: - try: - for output in self._run(input_data): - yield output - except Exception as e: - yield "error", str(e) + for output in self._run(input_data): + yield output def _run(self, input_data: Input) -> BlockOutput: chunks = self._split_text( @@ -642,24 +636,21 @@ def llm_call( raise ValueError(f"Unsupported LLM provider: {provider}") def run(self, input_data: Input, **kwargs) -> BlockOutput: - try: - api_key = ( - input_data.api_key.get_secret_value() - or LlmApiKeys[input_data.model.metadata.provider].get_secret_value() - ) + api_key = ( + input_data.api_key.get_secret_value() + or LlmApiKeys[input_data.model.metadata.provider].get_secret_value() + ) - messages = [message.model_dump() for message in input_data.messages] + messages = [message.model_dump() for message in input_data.messages] - response = self.llm_call( - api_key=api_key, - model=input_data.model, - messages=messages, - max_tokens=input_data.max_tokens, - ) + response = self.llm_call( + api_key=api_key, + model=input_data.model, + messages=messages, + max_tokens=input_data.max_tokens, + ) - yield "response", response - except Exception as e: - yield "error", f"Error calling LLM: {str(e)}" + yield "response", response class AIListGeneratorBlock(Block): @@ -777,9 +768,7 @@ def run(self, input_data: Input, **kwargs) -> BlockOutput: or LlmApiKeys[input_data.model.metadata.provider].get_secret_value() ) if not api_key_check: - logger.error("No LLM API key provided.") - yield "error", "No LLM API key provided." - return + raise ValueError("No LLM API key provided.") # Prepare the system prompt sys_prompt = """You are a Python list generator. Your task is to generate a Python list based on the user's prompt. @@ -873,7 +862,9 @@ def run(self, input_data: Input, **kwargs) -> BlockOutput: logger.error( f"Failed to generate a valid Python list after {input_data.max_retries} attempts" ) - yield "error", f"Failed to generate a valid Python list after {input_data.max_retries} attempts. Last error: {str(e)}" + raise RuntimeError( + f"Failed to generate a valid Python list after {input_data.max_retries} attempts. Last error: {str(e)}" + ) else: # Add a retry prompt logger.debug("Preparing retry prompt") diff --git a/autogpt_platform/backend/backend/blocks/medium.py b/autogpt_platform/backend/backend/blocks/medium.py index 9ca9b41bf40f..04ebe8fab012 100644 --- a/autogpt_platform/backend/backend/blocks/medium.py +++ b/autogpt_platform/backend/backend/blocks/medium.py @@ -138,31 +138,25 @@ def create_post( return response.json() def run(self, input_data: Input, **kwargs) -> BlockOutput: - try: - response = self.create_post( - input_data.api_key.get_secret_value(), - input_data.author_id.get_secret_value(), - input_data.title, - input_data.content, - input_data.content_format, - input_data.tags, - input_data.canonical_url, - input_data.publish_status, - input_data.license, - input_data.notify_followers, - ) - - if "data" in response: - yield "post_id", response["data"]["id"] - yield "post_url", response["data"]["url"] - yield "published_at", response["data"]["publishedAt"] - else: - error_message = response.get("errors", [{}])[0].get( - "message", "Unknown error occurred" - ) - yield "error", f"Failed to create Medium post: {error_message}" + response = self.create_post( + input_data.api_key.get_secret_value(), + input_data.author_id.get_secret_value(), + input_data.title, + input_data.content, + input_data.content_format, + input_data.tags, + input_data.canonical_url, + input_data.publish_status, + input_data.license, + input_data.notify_followers, + ) - except requests.RequestException as e: - yield "error", f"Network error occurred while creating Medium post: {str(e)}" - except Exception as e: - yield "error", f"Error occurred while creating Medium post: {str(e)}" + if "data" in response: + yield "post_id", response["data"]["id"] + yield "post_url", response["data"]["url"] + yield "published_at", response["data"]["publishedAt"] + else: + error_message = response.get("errors", [{}])[0].get( + "message", "Unknown error occurred" + ) + raise RuntimeError(f"Failed to create Medium post: {error_message}") diff --git a/autogpt_platform/backend/backend/blocks/replicate_flux_advanced.py b/autogpt_platform/backend/backend/blocks/replicate_flux_advanced.py index 6795c3c587d1..38abc8da20f0 100644 --- a/autogpt_platform/backend/backend/blocks/replicate_flux_advanced.py +++ b/autogpt_platform/backend/backend/blocks/replicate_flux_advanced.py @@ -139,24 +139,21 @@ def run(self, input_data: Input, **kwargs) -> BlockOutput: if seed is None: seed = int.from_bytes(os.urandom(4), "big") - try: - # Run the model using the provided inputs - result = self.run_model( - api_key=input_data.api_key.get_secret_value(), - model_name=input_data.replicate_model_name.api_name, - prompt=input_data.prompt, - seed=seed, - steps=input_data.steps, - guidance=input_data.guidance, - interval=input_data.interval, - aspect_ratio=input_data.aspect_ratio, - output_format=input_data.output_format, - output_quality=input_data.output_quality, - safety_tolerance=input_data.safety_tolerance, - ) - yield "result", result - except Exception as e: - yield "error", str(e) + # Run the model using the provided inputs + result = self.run_model( + api_key=input_data.api_key.get_secret_value(), + model_name=input_data.replicate_model_name.api_name, + prompt=input_data.prompt, + seed=seed, + steps=input_data.steps, + guidance=input_data.guidance, + interval=input_data.interval, + aspect_ratio=input_data.aspect_ratio, + output_format=input_data.output_format, + output_quality=input_data.output_quality, + safety_tolerance=input_data.safety_tolerance, + ) + yield "result", result def run_model( self, diff --git a/autogpt_platform/backend/backend/blocks/search.py b/autogpt_platform/backend/backend/blocks/search.py index f8fc783e56d7..ecd63e2ee658 100644 --- a/autogpt_platform/backend/backend/blocks/search.py +++ b/autogpt_platform/backend/backend/blocks/search.py @@ -36,20 +36,12 @@ def __init__(self): ) def run(self, input_data: Input, **kwargs) -> BlockOutput: - try: - topic = input_data.topic - url = f"https://en.wikipedia.org/api/rest_v1/page/summary/{topic}" - response = self.get_request(url, json=True) - yield "summary", response["extract"] - - except requests.exceptions.HTTPError as http_err: - yield "error", f"HTTP error occurred: {http_err}" - - except requests.RequestException as e: - yield "error", f"Request to Wikipedia failed: {e}" - - except KeyError as e: - yield "error", f"Error parsing Wikipedia response: {e}" + topic = input_data.topic + url = f"https://en.wikipedia.org/api/rest_v1/page/summary/{topic}" + response = self.get_request(url, json=True) + if "extract" not in response: + raise RuntimeError(f"Unable to parse Wikipedia response: {response}") + yield "summary", response["extract"] class SearchTheWebBlock(Block, GetRequest): @@ -73,24 +65,17 @@ def __init__(self): ) def run(self, input_data: Input, **kwargs) -> BlockOutput: - try: - # Encode the search query - encoded_query = quote(input_data.query) + # Encode the search query + encoded_query = quote(input_data.query) - # Prepend the Jina Search URL to the encoded query - jina_search_url = f"https://s.jina.ai/{encoded_query}" + # Prepend the Jina Search URL to the encoded query + jina_search_url = f"https://s.jina.ai/{encoded_query}" - # Make the request to Jina Search - response = self.get_request(jina_search_url, json=False) + # Make the request to Jina Search + response = self.get_request(jina_search_url, json=False) - # Output the search results - yield "results", response - - except requests.exceptions.HTTPError as http_err: - yield "error", f"HTTP error occurred: {http_err}" - - except requests.RequestException as e: - yield "error", f"Request to Jina Search failed: {e}" + # Output the search results + yield "results", response class ExtractWebsiteContentBlock(Block, GetRequest): @@ -125,13 +110,8 @@ def run(self, input_data: Input, **kwargs) -> BlockOutput: else: url = f"https://r.jina.ai/{input_data.url}" - try: - content = self.get_request(url, json=False) - yield "content", content - except requests.exceptions.HTTPError as http_err: - yield "error", f"HTTP error occurred: {http_err}" - except requests.RequestException as e: - yield "error", f"Request to URL failed: {e}" + content = self.get_request(url, json=False) + yield "content", content class GetWeatherInformationBlock(Block, GetRequest): @@ -171,26 +151,15 @@ def __init__(self): ) def run(self, input_data: Input, **kwargs) -> BlockOutput: - try: - units = "metric" if input_data.use_celsius else "imperial" - api_key = input_data.api_key.get_secret_value() - location = input_data.location - url = f"http://api.openweathermap.org/data/2.5/weather?q={quote(location)}&appid={api_key}&units={units}" - weather_data = self.get_request(url, json=True) - - if "main" in weather_data and "weather" in weather_data: - yield "temperature", str(weather_data["main"]["temp"]) - yield "humidity", str(weather_data["main"]["humidity"]) - yield "condition", weather_data["weather"][0]["description"] - else: - yield "error", f"Expected keys not found in response: {weather_data}" - - except requests.exceptions.HTTPError as http_err: - if http_err.response.status_code == 403: - yield "error", "Request to weather API failed: 403 Forbidden. Check your API key and permissions." - else: - yield "error", f"HTTP error occurred: {http_err}" - except requests.RequestException as e: - yield "error", f"Request to weather API failed: {e}" - except KeyError as e: - yield "error", f"Error processing weather data: {e}" + units = "metric" if input_data.use_celsius else "imperial" + api_key = input_data.api_key.get_secret_value() + location = input_data.location + url = f"http://api.openweathermap.org/data/2.5/weather?q={quote(location)}&appid={api_key}&units={units}" + weather_data = self.get_request(url, json=True) + + if "main" in weather_data and "weather" in weather_data: + yield "temperature", str(weather_data["main"]["temp"]) + yield "humidity", str(weather_data["main"]["humidity"]) + yield "condition", weather_data["weather"][0]["description"] + else: + raise RuntimeError(f"Expected keys not found in response: {weather_data}") diff --git a/autogpt_platform/backend/backend/blocks/talking_head.py b/autogpt_platform/backend/backend/blocks/talking_head.py index e1851ae030fd..e93b69ed8547 100644 --- a/autogpt_platform/backend/backend/blocks/talking_head.py +++ b/autogpt_platform/backend/backend/blocks/talking_head.py @@ -106,41 +106,40 @@ def get_clip_status(self, api_key: str, clip_id: str) -> dict: return response.json() def run(self, input_data: Input, **kwargs) -> BlockOutput: - try: - # Create the clip - payload = { - "script": { - "type": "text", - "subtitles": str(input_data.subtitles).lower(), - "provider": { - "type": input_data.provider, - "voice_id": input_data.voice_id, - }, - "ssml": str(input_data.ssml).lower(), - "input": input_data.script_input, + # Create the clip + payload = { + "script": { + "type": "text", + "subtitles": str(input_data.subtitles).lower(), + "provider": { + "type": input_data.provider, + "voice_id": input_data.voice_id, }, - "config": {"result_format": input_data.result_format}, - "presenter_config": {"crop": {"type": input_data.crop_type}}, - "presenter_id": input_data.presenter_id, - "driver_id": input_data.driver_id, - } + "ssml": str(input_data.ssml).lower(), + "input": input_data.script_input, + }, + "config": {"result_format": input_data.result_format}, + "presenter_config": {"crop": {"type": input_data.crop_type}}, + "presenter_id": input_data.presenter_id, + "driver_id": input_data.driver_id, + } - response = self.create_clip(input_data.api_key.get_secret_value(), payload) - clip_id = response["id"] + response = self.create_clip(input_data.api_key.get_secret_value(), payload) + clip_id = response["id"] - # Poll for clip status - for _ in range(input_data.max_polling_attempts): - status_response = self.get_clip_status( - input_data.api_key.get_secret_value(), clip_id + # Poll for clip status + for _ in range(input_data.max_polling_attempts): + status_response = self.get_clip_status( + input_data.api_key.get_secret_value(), clip_id + ) + if status_response["status"] == "done": + yield "video_url", status_response["result_url"] + return + elif status_response["status"] == "error": + raise RuntimeError( + f"Clip creation failed: {status_response.get('error', 'Unknown error')}" ) - if status_response["status"] == "done": - yield "video_url", status_response["result_url"] - return - elif status_response["status"] == "error": - yield "error", f"Clip creation failed: {status_response.get('error', 'Unknown error')}" - return - time.sleep(input_data.polling_interval) - yield "error", "Clip creation timed out" - except Exception as e: - yield "error", str(e) + time.sleep(input_data.polling_interval) + + raise TimeoutError("Clip creation timed out") diff --git a/autogpt_platform/backend/backend/blocks/text_to_speech_block.py b/autogpt_platform/backend/backend/blocks/text_to_speech_block.py index 5239142ee297..41412763407f 100644 --- a/autogpt_platform/backend/backend/blocks/text_to_speech_block.py +++ b/autogpt_platform/backend/backend/blocks/text_to_speech_block.py @@ -68,12 +68,9 @@ def call_unreal_speech_api( return response.json() def run(self, input_data: Input, **kwargs) -> BlockOutput: - try: - api_response = self.call_unreal_speech_api( - input_data.api_key.get_secret_value(), - input_data.text, - input_data.voice_id, - ) - yield "mp3_url", api_response["OutputUri"] - except Exception as e: - yield "error", str(e) + api_response = self.call_unreal_speech_api( + input_data.api_key.get_secret_value(), + input_data.text, + input_data.voice_id, + ) + yield "mp3_url", api_response["OutputUri"] diff --git a/autogpt_platform/backend/backend/blocks/youtube.py b/autogpt_platform/backend/backend/blocks/youtube.py index e299b121aa4a..cec50109bd4d 100644 --- a/autogpt_platform/backend/backend/blocks/youtube.py +++ b/autogpt_platform/backend/backend/blocks/youtube.py @@ -64,14 +64,11 @@ def get_transcript(video_id: str): return YouTubeTranscriptApi.get_transcript(video_id) def run(self, input_data: Input, **kwargs) -> BlockOutput: - try: - video_id = self.extract_video_id(input_data.youtube_url) - yield "video_id", video_id + video_id = self.extract_video_id(input_data.youtube_url) + yield "video_id", video_id - transcript = self.get_transcript(video_id) - formatter = TextFormatter() - transcript_text = formatter.format_transcript(transcript) + transcript = self.get_transcript(video_id) + formatter = TextFormatter() + transcript_text = formatter.format_transcript(transcript) - yield "transcript", transcript_text - except Exception as e: - yield "error", str(e) + yield "transcript", transcript_text diff --git a/autogpt_platform/backend/backend/data/block.py b/autogpt_platform/backend/backend/data/block.py index 554dfc237c09..594fd10e7681 100644 --- a/autogpt_platform/backend/backend/data/block.py +++ b/autogpt_platform/backend/backend/data/block.py @@ -272,8 +272,8 @@ def execute(self, input_data: BlockInput, **kwargs) -> BlockOutput: for output_name, output_data in self.run( self.input_schema(**input_data), **kwargs ): - if "error" in output_name: - raise ValueError(output_data) + if output_name == "error": + raise RuntimeError(output_data) if error := self.output_schema.validate_field(output_name, output_data): raise ValueError(f"Block produced an invalid output data: {error}") yield output_name, output_data diff --git a/docs/content/server/new_blocks.md b/docs/content/server/new_blocks.md index a3085f1a7e92..a56675810b0a 100644 --- a/docs/content/server/new_blocks.md +++ b/docs/content/server/new_blocks.md @@ -44,7 +44,7 @@ Follow these steps to create and test a new block: class Output(BlockSchema): summary: str # The summary of the topic from Wikipedia - error: str # Any error message if the request fails + error: str # Any error message if the request fails, error field needs to be named `error`. ``` 4. **Implement the `__init__` method, including test data and mocks:** @@ -95,17 +95,13 @@ Follow these steps to create and test a new block: yield "summary", response['extract'] except requests.exceptions.HTTPError as http_err: - yield "error", f"HTTP error occurred: {http_err}" - except requests.RequestException as e: - yield "error", f"Request to Wikipedia failed: {e}" - except KeyError as e: - yield "error", f"Error parsing Wikipedia response: {e}" + raise RuntimeError(f"HTTP error occurred: {http_err}") ``` - **Try block**: Contains the main logic to fetch and process the Wikipedia summary. - **API request**: Send a GET request to the Wikipedia API. - - **Error handling**: Handle various exceptions that might occur during the API request and data processing. - - **Yield**: Use `yield` to output the results. Prefer to output one result object at a time. If you are calling a function that returns a list, you can yield each item in the list separately. You can also yield the whole list as well, but do both rather than yielding the list. For example: If you were writing a block that outputs emails, you'd yield each email as a separate result object, but you could also yield the whole list as an additional single result object. + - **Error handling**: Handle various exceptions that might occur during the API request and data processing. We don't need to catch all exceptions, only the ones we expect and can handle. The uncaught exceptions will be automatically yielded as `error` in the output. Any block that raises an exception (or yields an `error` output) will be marked as failed. Prefer raising exceptions over yielding `error`, as it will stop the execution immediately. + - **Yield**: Use `yield` to output the results. Prefer to output one result object at a time. If you are calling a function that returns a list, you can yield each item in the list separately. You can also yield the whole list as well, but do both rather than yielding the list. For example: If you were writing a block that outputs emails, you'd yield each email as a separate result object, but you could also yield the whole list as an additional single result object. Yielding output named `error` will break the execution right away and mark the block execution as failed. ### Blocks with authentication