| 
									
										
										
										
											2025-04-05 23:27:48 +08:00
										 |  |  | """
 | 
					
						
							|  |  |  | GTSAM Copyright 2010-2025, Georgia Tech Research Corporation, | 
					
						
							|  |  |  | Atlanta, Georgia 30332-0415 | 
					
						
							|  |  |  | All Rights Reserved | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | See LICENSE for the license information | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Author: Porter Zach | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | This script generates interactive Python notebooks (.ipynb) that document GTSAM  | 
					
						
							| 
									
										
										
										
											2025-04-06 03:20:19 +08:00
										 |  |  | header files. Since inserting the text of the file directly into the prompt | 
					
						
							|  |  |  | might be too many tokens, it retrieves the header file content from the GTSAM  | 
					
						
							|  |  |  | GitHub repository. It then sends it to OpenAI's API for processing, and saves  | 
					
						
							|  |  |  | the generated documentation as a Jupyter notebook. | 
					
						
							| 
									
										
										
										
											2025-04-05 23:27:48 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | Functions: | 
					
						
							|  |  |  |     is_url_valid(url: str) -> bool: | 
					
						
							|  |  |  |         Verifies that the supplied URL does not return a 404. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     save_ipynb(text: str, file_path: str) -> str: | 
					
						
							|  |  |  |         Saves the provided text to a single Markdown cell in a new .ipynb file. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     generate_ipynb(file_path: str, openai_client): | 
					
						
							|  |  |  |         Generates an interactive Python notebook for the given GTSAM header file  | 
					
						
							|  |  |  |         by sending a request to OpenAI's API and saving the response. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Usage: | 
					
						
							|  |  |  |     Run the script with paths to GTSAM header files as arguments. For example: | 
					
						
							|  |  |  |         python gpt_generate.py gtsam/geometry/Pose3.h | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-04 04:38:20 +08:00
										 |  |  | import os | 
					
						
							|  |  |  | import time | 
					
						
							|  |  |  | import requests | 
					
						
							|  |  |  | import argparse | 
					
						
							|  |  |  | import nbformat as nbf | 
					
						
							|  |  |  | from openai import OpenAI | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | _output_folder = "output" | 
					
						
							|  |  |  | _gtsam_gh_base = "https://raw.githubusercontent.com/borglab/gtsam/refs/heads/develop/" | 
					
						
							|  |  |  | _asst_id = "asst_na7wYBtXyGU0x5t2RdcnpxzP" | 
					
						
							|  |  |  | _request_text = "Document the file found at {}." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def is_url_valid(url): | 
					
						
							|  |  |  |     """Verify that the supplied URL does not return a 404.""" | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         response = requests.head(url, allow_redirects=True) | 
					
						
							|  |  |  |         return response.status_code != 404 | 
					
						
							|  |  |  |     except requests.RequestException: | 
					
						
							|  |  |  |         return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def save_ipynb(text: str, file_path: str): | 
					
						
							|  |  |  |     """Save text to a single Markdown cell in a new .ipynb file.""" | 
					
						
							|  |  |  |     script_dir = os.path.dirname(os.path.abspath(__file__)) | 
					
						
							|  |  |  |     output_dir = os.path.join(script_dir, _output_folder) | 
					
						
							|  |  |  |     os.makedirs(output_dir, exist_ok=True) | 
					
						
							|  |  |  |     output_file = os.path.splitext(os.path.basename(file_path))[0] + ".ipynb" | 
					
						
							|  |  |  |     output_full_path = os.path.join(output_dir, output_file) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     nb = nbf.v4.new_notebook() | 
					
						
							|  |  |  |     new_cell = nbf.v4.new_markdown_cell(text) | 
					
						
							|  |  |  |     nb['cells'].append(new_cell) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     with open(output_full_path, 'w', encoding='utf-8') as file: | 
					
						
							|  |  |  |         nbf.write(nb, file) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return output_file | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def generate_ipynb(file_path: str, openai_client): | 
					
						
							|  |  |  |     """Generate an interactive Python notebook for the given GTSAM header file.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Args: | 
					
						
							|  |  |  |         file_path (str): The fully-qualified path from the root of the gtsam  | 
					
						
							|  |  |  |             repository to the header file that will be documented. | 
					
						
							|  |  |  |         openai_client (openai.OpenAI): The OpenAI client to use. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     # Create the URL to get the header file from. | 
					
						
							|  |  |  |     url = _gtsam_gh_base + file_path | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if not is_url_valid(url): | 
					
						
							|  |  |  |         print(f"{url} was not found on the server, or an error occurred.") | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     print(f"Sending request to OpenAI to document {url}.") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Create a new thread and send the request | 
					
						
							|  |  |  |     thread = openai_client.beta.threads.create() | 
					
						
							|  |  |  |     openai_client.beta.threads.messages.create( | 
					
						
							|  |  |  |         thread_id=thread.id, role="user", content=_request_text.format(url)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     run = openai_client.beta.threads.runs.create(thread_id=thread.id, | 
					
						
							|  |  |  |                                                  assistant_id=_asst_id) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     print("Waiting for the assistant to process the request...") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Wait for request to be processed | 
					
						
							|  |  |  |     while True: | 
					
						
							|  |  |  |         run_status = openai_client.beta.threads.runs.retrieve( | 
					
						
							|  |  |  |             thread_id=thread.id, run_id=run.id) | 
					
						
							|  |  |  |         if run_status.status == "completed": | 
					
						
							|  |  |  |             break | 
					
						
							|  |  |  |         time.sleep(2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     print("Request processed. Retrieving response...") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Fetch messages | 
					
						
							|  |  |  |     messages = openai_client.beta.threads.messages.list(thread_id=thread.id) | 
					
						
							|  |  |  |     # Retrieve response text and strip ```markdown ... ``` | 
					
						
							|  |  |  |     text = messages.data[0].content[0].text.value.strip('`').strip('markdown') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Write output to file | 
					
						
							|  |  |  |     output_filename = save_ipynb(text, file_path) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     print(f"Response retrieved. Find output in {output_filename}.") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == "__main__": | 
					
						
							|  |  |  |     parser = argparse.ArgumentParser( | 
					
						
							|  |  |  |         prog="gpt_generate", | 
					
						
							|  |  |  |         description= | 
					
						
							|  |  |  |         "Generates .ipynb documentation files given paths to GTSAM header files." | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |     parser.add_argument( | 
					
						
							|  |  |  |         "file_paths", | 
					
						
							|  |  |  |         nargs='+', | 
					
						
							| 
									
										
										
										
											2025-04-04 05:00:44 +08:00
										 |  |  |         help= | 
					
						
							|  |  |  |         "The paths to the header files from the root gtsam directory, e.g. 'gtsam/geometry/Pose3.h'." | 
					
						
							|  |  |  |     ) | 
					
						
							| 
									
										
										
										
											2025-04-04 04:38:20 +08:00
										 |  |  |     args = parser.parse_args() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Retrieves API key from environment variable OPENAI_API_KEY | 
					
						
							|  |  |  |     client = OpenAI() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for file_path in args.file_paths: | 
					
						
							|  |  |  |         generate_ipynb(file_path, client) |