nikhmr1235 commited on
Commit
d47a09f
·
verified ·
1 Parent(s): aa133b0

update nodes with changes lost during refactoring

Browse files
Files changed (1) hide show
  1. src/langgraph_logic/nodes.py +251 -57
src/langgraph_logic/nodes.py CHANGED
@@ -36,39 +36,85 @@ logging.basicConfig(
36
  # --- GitHub API Functions ---
37
 
38
  def fetch_pr_code_changes(repo_name: str, pr_id: int) -> Tuple[Optional[str], Optional[Dict[str, str]], Optional[str], Optional[str]]:
39
- """Fetches the raw diff, file contents, and head commit SHA for a PR."""
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  github_token = git_hub_token
 
41
  if not github_token:
42
- return None, None, None, "GitHub token not found."
 
43
 
44
  try:
45
  g = Github(github_token)
46
  repo = g.get_repo(repo_name)
47
  pull_request = repo.get_pull(pr_id)
 
 
48
  head_commit_sha = pull_request.head.sha
 
49
 
 
 
 
50
  patch_url = pull_request.patch_url
51
  headers = {"Authorization": f"token {github_token}"}
52
  raw_diff_content = requests.get(patch_url, headers=headers).text
53
 
 
54
  file_contents: Dict[str, str] = {}
55
  for file in pull_request.get_files():
 
56
  if file.status == 'deleted':
57
  file_contents[file.filename] = "[FILE DELETED]"
58
  continue
 
59
  try:
 
 
 
 
60
  file_content_obj = repo.get_contents(file.filename, ref=pull_request.head.sha)
 
61
  if isinstance(file_content_obj, list):
 
62
  file_contents[file.filename] = "[DIRECTORY OR MULTIPLE FILES]"
63
  continue
 
64
  file_contents[file.filename] = file_content_obj.decoded_content.decode('utf-8')
 
65
  except GithubException as e:
66
- file_contents[file.filename] = f"[ERROR: Could not fetch content. Status: {e.status}]"
67
-
68
- return raw_diff_content, file_contents, head_commit_sha, None
69
- except (UnknownObjectException, GithubException, Exception) as e:
70
- error_msg = f"Error fetching PR data: {e}"
71
- logging.error(error_msg)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  return None, None, None, error_msg
73
 
74
  def post_review_comments_on_github(
@@ -151,29 +197,95 @@ def update_submitted_review_body(
151
  # --- LLM and Parsing Functions ---
152
 
153
  def generate_code_review_markdown(code_diff: str, file_contents: Dict[str, str]) -> str:
154
- """Generates a detailed, human-readable code review in Markdown format from the LLM."""
 
 
 
 
 
 
 
 
 
 
 
 
155
  full_contents_str = ""
156
  if file_contents:
157
  for filename, content in file_contents.items():
 
158
  full_contents_str += f"--- Full Content of {filename} ---\n```python\n{content}\n```\n\n"
159
-
160
- prompt = ChatPromptTemplate.from_messages([
161
- ("system", "You are an expert Senior Software Engineer..."), # Truncated for brevity
162
- ("human", "Here are the code changes (diff):\n```{code_diff}```\n\nHere are the full contents...\n{full_contents_context}\n\nPlease provide your structured code review in Markdown.")
163
- ])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  review_chain = prompt | llm
 
 
165
  try:
166
- return review_chain.invoke({"code_diff": code_diff, "full_contents_context": full_contents_str}).content
 
 
 
 
167
  except Exception as e:
168
- return f"Error generating code review: {e}"
 
 
169
 
 
170
  def _extract_suggestion(text: str) -> Tuple[Optional[str], str]:
 
171
  suggestion_match = re.search(r"```suggestion\n([\s\S]*?)\n```", text, re.MULTILINE)
172
  suggestion_code = suggestion_match.group(1).strip() if suggestion_match else None
 
 
173
  cleaned_message = re.sub(r"```suggestion[\s\S]*?```", "", text).strip()
174
  return suggestion_code, cleaned_message
175
 
 
176
  def _parse_bullet_comments(text_block: str) -> List[ParsedComment]:
 
177
  comments = []
178
  comment_matches = re.finditer(r"(^ *[-*]\s*[\s\S]*?)(?=\n *[-*]\s*|\Z)", text_block, re.MULTILINE | re.DOTALL)
179
  for cm in comment_matches:
@@ -290,72 +402,154 @@ def parse_llm_review_markdown(markdown_review: str) -> LLMReviewOutput:
290
 
291
  # --- Graph Nodes ---
292
 
293
- def code_retriever_node(state: PRReviewState) -> PRReviewState:
294
- """Fetches code changes from the PR."""
295
- logging.info("--- NODE: code_retriever_node ---")
296
- diff, contents, _, error = fetch_pr_code_changes(state.repo_name, state.pr_id)
297
- if error:
298
- return state.model_copy(update={"review_status": "error", "last_error": error})
299
- return state.model_copy(update={"review_status": "code_fetched", "code_diff": diff, "file_contents": contents})
300
-
301
- def code_reviewer_node(state: PRReviewState) -> PRReviewState:
302
- """Generates a code review using the LLM."""
303
- logging.info("--- NODE: code_reviewer_node ---")
304
- review_markdown = generate_code_review_markdown(state.code_diff, state.file_contents)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
  # --- DEBUG LOGGING START ---
306
  print("\n" + "="*50)
307
  print("--- RAW LLM MARKDOWN OUTPUT ---")
308
  print(review_markdown)
309
  print("="*50 + "\n")
310
  # --- DEBUG LOGGING END ---
311
- return state.model_copy(update={"review_status": "code_reviewed", "llm_markdown_review": review_markdown})
312
 
313
- def feedback_formatter_node(state: PRReviewState) -> PRReviewState:
314
- """Parses the raw LLM review into a structured format."""
315
- logging.info("--- NODE: feedback_formatter_node ---")
316
- parsed_data = parse_llm_review_markdown(state.llm_markdown_review)
317
- return state.model_copy(update={"review_status": "review_parsed", "parsed_llm_review_data": parsed_data})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
 
319
  def post_code_review_node(state: PRReviewState) -> PRReviewState:
320
- """Posts the review to GitHub."""
321
- logging.info("--- NODE: post_code_review_node ---")
 
 
322
  if not state.parsed_llm_review_data:
323
- return state.model_copy(update={"review_status": "error", "last_error": "Parsed review data is missing."})
 
 
 
 
 
324
  try:
 
325
  result = post_review_comments_on_github(
326
- repo_name=state.repo_name, pr_id=state.pr_id,
327
- parsed_review_data=state.parsed_llm_review_data, github_token=git_hub_token
 
 
328
  )
 
 
329
  return state.model_copy(update={
330
- "review_status": "initial_review_posted", "original_review_id": result['review_id'],
331
- "original_review_url": result['review_url'], "main_comment_body": result['main_comment_body']
 
 
 
332
  })
333
  except Exception as e:
334
- return state.model_copy(update={"review_status": "error", "last_error": f"Failed to post review: {e}"})
 
 
 
 
 
 
335
 
336
  def update_review_body_based_on_human_input_node(state: PRReviewState) -> PRReviewState:
337
- """Updates the review body based on human feedback."""
338
- logging.info("--- NODE: update_review_body_based_on_human_input_node ---")
 
 
 
 
 
339
  if not state.require_human_approval:
 
 
340
  return state
341
-
342
- decision = "approved" if state.human_approval_status else "rejected"
343
- feedback = f"Human Feedback: {state.human_feedback_message}\n\n" if state.human_feedback_message else ""
344
- prefix = f"**Human Decision:** {decision}\n---\n\n{feedback}"
345
-
346
- if state.human_approval_status:
347
- updated_body = f"{prefix}Please go ahead and incorporate these Automated Bots review comments\n\n{state.main_comment_body}"
 
 
 
 
 
 
 
 
 
 
348
  else:
349
- updated_body = f"{prefix}Please IGNORE these Automated Bots review comments...\n\n{state.main_comment_body}"
 
350
 
351
  try:
 
352
  result = update_submitted_review_body(
353
- repo_name=state.repo_name, pr_id=state.pr_id, review_id=state.original_review_id,
354
- new_body=updated_body, github_token=git_hub_token
 
 
 
355
  )
 
 
356
  return state.model_copy(update={
357
- "review_status": "review_submitted", "final_review_id": result['review_id'],
358
- "final_review_url": result['review_url'], "main_comment_body": result['updated_body']
 
 
 
359
  })
360
  except Exception as e:
361
- return state.model_copy(update={"review_status": "error", "last_error": f"Failed to update review: {e}"})
 
 
 
 
 
 
36
  # --- GitHub API Functions ---
37
 
38
  def fetch_pr_code_changes(repo_name: str, pr_id: int) -> Tuple[Optional[str], Optional[Dict[str, str]], Optional[str], Optional[str]]:
39
+ """
40
+ Fetches the raw diff content, the full contents of changed files,
41
+ and the head commit SHA for a given PR.
42
+ Args:
43
+ repo_name (str): The full name of the repository (e.g., "octocat/Spoon-Knife").
44
+ pr_id (int): The ID of the Pull Request.
45
+ Returns:
46
+ Tuple[Optional[str], Optional[Dict[str, str]], Optional[str], Optional[str]]:
47
+ - raw_diff_content (str or None): The raw diff content of the PR.
48
+ - file_contents (Dict[str, str] or None): Dictionary mapping filename to its full content (after changes).
49
+ - head_commit_sha (str or None): The SHA of the head commit of the PR.
50
+ - error_message (str or None): An error message if something went wrong.
51
+ """
52
+ #github_token = os.getenv("GITHUB_TOKEN")
53
  github_token = git_hub_token
54
+
55
  if not github_token:
56
+ print("Error: GITHUB_TOKEN environment variable not set.")
57
+ return None, None, None, "GitHub token not found in environment variables."
58
 
59
  try:
60
  g = Github(github_token)
61
  repo = g.get_repo(repo_name)
62
  pull_request = repo.get_pull(pr_id)
63
+
64
+ # --- NEW: Get the head commit SHA ---
65
  head_commit_sha = pull_request.head.sha
66
+ print(f"Fetched PR {pr_id} head commit SHA: {head_commit_sha}")
67
 
68
+
69
+ # 1. Fetch raw diff content (patch)
70
+ # Using requests directly for patch_url is good as PyGithub's get_patch() can sometimes be rate-limited differently
71
  patch_url = pull_request.patch_url
72
  headers = {"Authorization": f"token {github_token}"}
73
  raw_diff_content = requests.get(patch_url, headers=headers).text
74
 
75
+ # 2. Fetch full content of changed files
76
  file_contents: Dict[str, str] = {}
77
  for file in pull_request.get_files():
78
+ # Skip files that were deleted, as their content cannot be retrieved from the current head.
79
  if file.status == 'deleted':
80
  file_contents[file.filename] = "[FILE DELETED]"
81
  continue
82
+
83
  try:
84
+ # We want the content *after* the change, which is from the PR's head branch.
85
+ # PyGithub's get_contents should be called with `ref` set to `pull_request.head.ref`
86
+ # or `pull_request.head.sha` for explicit content at the PR's head.
87
+ # Using pull_request.head.sha is more robust as ref might change.
88
  file_content_obj = repo.get_contents(file.filename, ref=pull_request.head.sha)
89
+
90
  if isinstance(file_content_obj, list):
91
+ print(f"Warning: '{file.filename}' is a directory or multiple files, skipping content retrieval for now.")
92
  file_contents[file.filename] = "[DIRECTORY OR MULTIPLE FILES]"
93
  continue
94
+
95
  file_contents[file.filename] = file_content_obj.decoded_content.decode('utf-8')
96
+
97
  except GithubException as e:
98
+ print(f"Warning: GitHub API error fetching content for {file.filename} (PR {pr_id}, Repo {repo_name}): {e.status} - {e.data.get('message', 'No message')}")
99
+ file_contents[file.filename] = f"[ERROR: Could not fetch content. Status: {e.status}, Message: {e.data.get('message', 'No message')}]"
100
+ except Exception as e:
101
+ print(f"Unexpected error fetching content for {file.filename} (PR {pr_id}, Repo {repo_name}): {e}")
102
+ file_contents[file.filename] = f"[ERROR: Unexpected error fetching content: {e}]"
103
+
104
+ # Return the new head_commit_sha along with existing returns
105
+ return raw_diff_content, file_contents, head_commit_sha, None # No error message if successful
106
+
107
+ except UnknownObjectException as e:
108
+ error_msg = f"GitHub object not found (repo or PR): {e.data.get('message', 'No message')}"
109
+ print(f"Error in fetch_pr_code_changes: {error_msg}")
110
+ return None, None, None, error_msg
111
+ except GithubException as e:
112
+ error_msg = f"GitHub API error for PR {pr_id} from {repo_name}: {e.status} - {e.data.get('message', 'No message')}"
113
+ print(f"Error in fetch_pr_code_changes: {error_msg}")
114
+ return None, None, None, error_msg
115
+ except Exception as e:
116
+ error_msg = f"An unexpected error occurred while fetching PR {pr_id} from {repo_name}: {e}"
117
+ print(f"Error in fetch_pr_code_changes: {error_msg}")
118
  return None, None, None, error_msg
119
 
120
  def post_review_comments_on_github(
 
197
  # --- LLM and Parsing Functions ---
198
 
199
  def generate_code_review_markdown(code_diff: str, file_contents: Dict[str, str]) -> str:
200
+ """
201
+ Generates a detailed, human-readable code review in Markdown format from the LLM.
202
+ The prompt is designed to elicit structured Markdown output that can then be
203
+ parsed for GitHub PR comments, grouped by file and function.
204
+ Args:
205
+ code_diff (str): The string representation of the code diff.
206
+ file_contents (Dict[str, str]): A dictionary where keys are file paths
207
+ and values are their full content.
208
+ Returns:
209
+ str: A Markdown string representing the code review.
210
+ """
211
+
212
+ # Prepare full contents context
213
  full_contents_str = ""
214
  if file_contents:
215
  for filename, content in file_contents.items():
216
+ # Add a clear separator and Markdown code block for each file
217
  full_contents_str += f"--- Full Content of {filename} ---\n```python\n{content}\n```\n\n"
218
+ else:
219
+ full_contents_str = "No full file contents provided for additional context."
220
+
221
+ # Construct the Prompt Template
222
+ prompt = ChatPromptTemplate.from_messages(
223
+ [
224
+ ("system",
225
+ "You are an expert Senior Software Engineer and a meticulous code reviewer.\n"
226
+ "Your task is to review the provided code changes in a Pull Request.\n"
227
+ "Analyze the `code_diff` for potential bugs, performance issues, security vulnerabilities, code style violations, maintainability concerns, and missing tests or documentation.\n"
228
+ "Refer to the `full_file_contents` for additional context if the diff alone is insufficient to understand the changes or their implications.\n"
229
+ "Provide a comprehensive, actionable, and constructive review.\n"
230
+ "Format your review clearly using Markdown. Structure it with the following top-level sections:\n"
231
+ "1. **Overall Impression:** A brief summary of the PR's purpose and overall quality.\n"
232
+ "2. **Specific Observations and Suggestions:** Detailed feedback, grouped by file.\n"
233
+ " - Within each file's section, group related comments, ideally by function or logical block.\n"
234
+ " - For each observation/suggestion, include relevant line numbers from the *new* file for context (e.g., 'Line X-Y:').\n"
235
+ "3. **Potential Issues and Edge Cases:** Discuss any missed scenarios or potential problems.\n"
236
+ "4. **Security Implications:** Highlight any security concerns.\n"
237
+ "5. **Adherence to Best Practices (PEP 8):** Comment on style and best practice compliance.\n"
238
+ "6. **Performance Considerations:** Discuss performance aspects.\n"
239
+ "7. **Unit Testing Suggestions:** Recommend additional tests.\n"
240
+ "8. **Docstring/Comment Improvements:** Suggest documentation enhancements.\n"
241
+ "9. **Clarity and Conciseness:** Feedback on code readability.\n"
242
+ "10. **Summary:** A concise conclusion and recommended action (e.g., 'Approve', 'Request Changes', 'Comment').\n\n"
243
+ "For code suggestions, use GitHub's Markdown code block with 'suggestion' annotation, like this:\n"
244
+ "```suggestion\n"
245
+ "your_suggested_code_here\n"
246
+ "```\n"
247
+ "Ensure file paths are correctly formatted (e.g., `src/utils/data_processor.py`)."
248
+ ),
249
+ ("human",
250
+ "Here are the code changes (diff):\n"
251
+ "```diff\n"
252
+ "{code_diff}\n"
253
+ "```\n\n"
254
+ "Here are the full contents of the changed files (for additional context, use only if necessary to understand the diff):\n"
255
+ "{full_contents_context}\n\n"
256
+ "Please provide your structured code review in Markdown."
257
+ ),
258
+ ]
259
+ )
260
+
261
+ # Create the Chain
262
  review_chain = prompt | llm
263
+
264
+ # Invoke the Chain
265
  try:
266
+ review_markdown = review_chain.invoke({
267
+ "code_diff": code_diff,
268
+ "full_contents_context": full_contents_str
269
+ }).content # Access the content attribute for Chat model output
270
+ return review_markdown
271
  except Exception as e:
272
+ print(f"Error generating code review: {e}")
273
+ return f"Error: Could not generate code review. {e}\n\n" \
274
+ f"Please check the LLM API call or token limits."
275
 
276
+ # Helper to extract suggestion block and clean message (No change needed)
277
  def _extract_suggestion(text: str) -> Tuple[Optional[str], str]:
278
+ """Helper to extract suggestion block and clean message."""
279
  suggestion_match = re.search(r"```suggestion\n([\s\S]*?)\n```", text, re.MULTILINE)
280
  suggestion_code = suggestion_match.group(1).strip() if suggestion_match else None
281
+
282
+ # Remove suggestion from the main message
283
  cleaned_message = re.sub(r"```suggestion[\s\S]*?```", "", text).strip()
284
  return suggestion_code, cleaned_message
285
 
286
+ # Helper to parse bullet-point comments (No change needed, already uses ParsedComment)
287
  def _parse_bullet_comments(text_block: str) -> List[ParsedComment]:
288
+ """Helper to parse bullet-point comments from a given text block."""
289
  comments = []
290
  comment_matches = re.finditer(r"(^ *[-*]\s*[\s\S]*?)(?=\n *[-*]\s*|\Z)", text_block, re.MULTILINE | re.DOTALL)
291
  for cm in comment_matches:
 
402
 
403
  # --- Graph Nodes ---
404
 
405
+ def code_retriever_node(state:PRReviewState):
406
+ repo_name = state.repo_name
407
+ pull_req_id = state.pr_id
408
+
409
+ print(f"code_retriever_node started")
410
+ print(f"repo_name :{repo_name}-------- pull_req_id:{pull_req_id}")
411
+
412
+ diff, contents,head_commit_sha, error = fetch_pr_code_changes(repo_name, pull_req_id)
413
+
414
+ # Don't forget to return an updated state, as nodes in LangGraph should always do
415
+ # For this simple example, we'll just return a copy with an updated status
416
+ updated_state = state.model_copy(update={
417
+ "review_status": "code_fetched", # Update status after retrieval logic
418
+ "code_diff": diff,
419
+ "file_contents": contents
420
+ })
421
+ return updated_state
422
+
423
+ def code_reviewer_node(state:PRReviewState):
424
+ code_diff = state.code_diff
425
+ file_contents = state.file_contents
426
+
427
+ print(f"code_reviewer_node started")
428
+
429
+ review_markdown = generate_code_review_markdown(code_diff, file_contents)
430
+
431
  # --- DEBUG LOGGING START ---
432
  print("\n" + "="*50)
433
  print("--- RAW LLM MARKDOWN OUTPUT ---")
434
  print(review_markdown)
435
  print("="*50 + "\n")
436
  # --- DEBUG LOGGING END ---
 
437
 
438
+ # Don't forget to return an updated state, as nodes in LangGraph should always do
439
+ # For this simple example, we'll just return a copy with an updated status
440
+ updated_state = state.model_copy(update={
441
+ "review_status": "code_reviewed", # Update status after retrieval logic
442
+ "llm_markdown_review":review_markdown,
443
+ })
444
+ return updated_state
445
+
446
+ def feedback_formatter_node(state: PRReviewState):
447
+ print(f"feedback_formatter_node started")
448
+ llm_markdown_review = state.llm_markdown_review
449
+
450
+ parsed_llm_review_data = parse_llm_review_markdown(llm_markdown_review)
451
+
452
+ # Don't forget to return an updated state, as nodes in LangGraph should always do
453
+ # For this simple example, we'll just return a copy with an updated status
454
+ updated_state = state.model_copy(update={
455
+ "review_status": "review_parsed" ,# Update status after retrieval logic
456
+ "parsed_llm_review_data":parsed_llm_review_data,
457
+ })
458
+ return updated_state
459
 
460
  def post_code_review_node(state: PRReviewState) -> PRReviewState:
461
+ """
462
+ Posts the LLM-generated review as a PENDING GitHub review.
463
+ """
464
+ print("--- NODE: post_code_review_node ---")
465
  if not state.parsed_llm_review_data:
466
+ raise ValueError("Cannot post pending review: parsed_llm_review_data is missing.")
467
+
468
+ repo_name = state.repo_name
469
+ pr_id = state.pr_id
470
+ parsed_llm_review_data = state.parsed_llm_review_data
471
+
472
  try:
473
+ # Call the helper to post as PENDING
474
  result = post_review_comments_on_github(
475
+ repo_name=repo_name,
476
+ pr_id=pr_id,
477
+ parsed_review_data=parsed_llm_review_data,
478
+ github_token=git_hub_token,
479
  )
480
+
481
+ print(f"result from post_pending_review_node():result = {result}")
482
  return state.model_copy(update={
483
+ "review_status": "initial_review_posted",
484
+ "original_review_id": result['review_id'],
485
+ "original_review_url": result['review_url'],
486
+ "main_comment_body": result['main_comment_body'],
487
+ "last_error": None # Clear previous errors
488
  })
489
  except Exception as e:
490
+ #logging.error(f"Error posting pending review: {e}")
491
+ print(f"Error posting pending review: {e}")
492
+ return state.model_copy(update={
493
+ "review_status": "error",
494
+ "last_error": f"Failed to post pending review: {e}"
495
+ })
496
+
497
 
498
  def update_review_body_based_on_human_input_node(state: PRReviewState) -> PRReviewState:
499
+ """
500
+ Posts the LLM-generated review as a PENDING GitHub review.
501
+ """
502
+ print("--- NODE: update_review_body_based_on_human_input_node ---")
503
+ if not state.main_comment_body:
504
+ raise ValueError("Cannot update submitted review body: main_comment_body is missing.")
505
+
506
  if not state.require_human_approval:
507
+ print("require_human_approval is False so exiting this function")
508
+ logging.info("require_human_approval is False so exiting this function")
509
  return state
510
+
511
+ repo_name = state.repo_name
512
+ pr_id = state.pr_id
513
+ original_review_id = state.original_review_id
514
+ main_comment_body = state.main_comment_body
515
+
516
+ if state.human_approval_status is True:
517
+ if state.human_feedback_message is not None:
518
+ updated_review_body = f"""**Human Decision:** approved\n---\n\nHuman Feedback: {state.human_feedback_message}\n\n Please go ahead and incorporate these Automated Bots review comments\n\n{main_comment_body}"""
519
+ else:
520
+ updated_review_body = f"""**Human Decision:** approved\n---\n\n Please go ahead and incorporate these Automated Bots review comments\n\n{main_comment_body}"""
521
+
522
+ elif state.human_approval_status is False:
523
+ if state.human_feedback_message is not None:
524
+ updated_review_body = f"""**Human Decision:** Reject\n---\n\nHuman Feedback: {state.human_feedback_message}\n\n Please IGNORE these Automated Bots review comments and wait for new review comments from your team\n\n{main_comment_body}"""
525
+ else:
526
+ updated_review_body = f"""**Human Decision:** Reject\n---\n\nPlease IGNORE these Automated Bots review comments and wait for new review comments from your team\n\n{main_comment_body}"""
527
  else:
528
+ return state
529
+
530
 
531
  try:
532
+ # Call the helper to post as PENDING
533
  result = update_submitted_review_body(
534
+ repo_name=repo_name,
535
+ pr_id=pr_id,
536
+ review_id = original_review_id,
537
+ new_body =updated_review_body,
538
+ github_token=git_hub_token
539
  )
540
+
541
+ print(f"result from update_submitted_review_body_node():result = {result}")
542
  return state.model_copy(update={
543
+ "review_status": "review_submitted",
544
+ "final_review_id": result['review_id'],
545
+ "final_review_url": result['review_url'],
546
+ "main_comment_body": result['updated_body'],
547
+ "last_error": None # Clear previous errors
548
  })
549
  except Exception as e:
550
+ #logging.error(f"Error posting pending review: {e}")
551
+ print(f"Error posting pending review: {e}")
552
+ return state.model_copy(update={
553
+ "review_status": "error",
554
+ "last_error": f"Failed to post pending review: {e}"
555
+ })