Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f0109a3
Refactored code so that documentation is handled by separate class
DianaStrauss Jun 10, 2024
630f571
refactored code
DianaStrauss Jun 10, 2024
bef16c0
Adjusted prompt_engineer to create better prompts
DianaStrauss Jun 13, 2024
74062ff
Refactored documentation_handler.py to update .yaml file when it get …
DianaStrauss Jun 13, 2024
7591be3
Created SubmitHTTPMethod.py for better separation
DianaStrauss Jun 13, 2024
73fe5c4
Created Converter and parser for handeling yaml and json files
DianaStrauss Jun 13, 2024
430cb1f
Refactored converter and parser
DianaStrauss Jun 14, 2024
cef43e9
Added token count so that prompts are not too long -> WIP shorten pro…
DianaStrauss Jun 14, 2024
89956d7
Refactored code and added yamlFile.py
DianaStrauss Jun 17, 2024
e7ce9ae
Refactored code
DianaStrauss Jun 19, 2024
995b199
Added simple scoring to prompt engineer
DianaStrauss Jul 4, 2024
cbafdf2
changed order of setuo methods in simple_openai_documentation
DianaStrauss Jul 4, 2024
34593e3
changed order of setuo methods in simple_openai_documentation
DianaStrauss Jul 4, 2024
b95dd31
changed order of setuo methods in simple_openai_documentation
DianaStrauss Jul 4, 2024
e267621
Addition of examples works with redocly
DianaStrauss Jul 9, 2024
56bc5ff
Added yaml file assistant
DianaStrauss Jul 9, 2024
7c681af
Can create openapi spec with examples
DianaStrauss Jul 9, 2024
120b09f
Cleaned up code
DianaStrauss Jul 12, 2024
2fcca09
Refactor code
DianaStrauss Jul 12, 2024
29aa192
Refactor code
DianaStrauss Jul 12, 2024
b2632ab
Cleaned up code
DianaStrauss Jul 12, 2024
3af909a
Cleaned up code
DianaStrauss Jul 12, 2024
b1f9886
Cleaned up code
DianaStrauss Jul 12, 2024
5915187
Merge branch 'main' of https://github.com/DianaStrauss/hackingBuddyGP…
andreashappe Jul 22, 2024
8e58cad
Merge branch 'development' into DianaStrauss-main
andreashappe Jul 22, 2024
bbb8133
update dependencies
andreashappe Jul 22, 2024
fd4323e
some simple renames
andreashappe Jul 22, 2024
ec3a0ee
Fixed attribute initialization of use_cases and transparent types
Neverbolt Jul 26, 2024
0babd39
Refactored code and fixed import bugs in simple_web_api_testing and s…
DianaStrauss Aug 1, 2024
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Addition of examples works with redocly
  • Loading branch information
DianaStrauss committed Jul 9, 2024
commit e267621d900b18023e804595093d32e97fee5a91
24 changes: 13 additions & 11 deletions src/hackingBuddyGPT/capabilities/yamlFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class YAMLFile(Capability):
def describe(self) -> str:
return "Takes a Yaml file and updates it with the given information"

def __call__(self, yaml_str: str, updates: str) -> str:
def __call__(self, yaml_str: str) -> str:
"""
Updates a YAML string based on provided inputs and returns the updated YAML string.

Expand All @@ -26,17 +26,19 @@ def __call__(self, yaml_str: str, updates: str) -> str:
# Load the YAML content from string
data = yaml.safe_load(yaml_str)

print(f'Updates:{yaml_str}')

# Apply updates from the updates dictionary
for key, value in updates.items():
if key in data:
data[key] = value
else:
print(f"Warning: Key '{key}' not found in the original data. Adding new key.")
data[key] = value

# Convert the updated dictionary back into a YAML string
updated_yaml_str = yaml.safe_dump(data, sort_keys=False)
return updated_yaml_str
#for key, value in updates.items():
# if key in data:
# data[key] = value
# else:
# print(f"Warning: Key '{key}' not found in the original data. Adding new key.")
# data[key] = value
#
## Convert the updated dictionary back into a YAML string
#updated_yaml_str = yaml.safe_dump(data, sort_keys=False)
#return updated_yaml_str
except yaml.YAMLError as e:
print(f"Error processing YAML data: {e}")
return "None"
122 changes: 93 additions & 29 deletions src/hackingBuddyGPT/usecases/web_api_testing/documentation_handler.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
import os
import re

from bs4 import BeautifulSoup
import openai
import pydantic_core
import yaml
Expand All @@ -26,7 +27,8 @@ def __init__(self, llm, capabilities):
"description": "Automatically generated description of the API."
},
"servers": [{"url": "https://jsonplaceholder.typicode.com"}],
"endpoints": {}
"endpoints": {},
"components": {"schemas": {}}
}
self.llm = llm
self.api_key = llm.api_key
Expand All @@ -39,24 +41,24 @@ def __init__(self, llm, capabilities):
"yaml": YAMLFile()
}


def update_openapi_spec(self, resp):
def update_openapi_spec(self, resp, result):
"""
Updates the OpenAPI specification based on the API response provided.

Args:
- response: The response object containing details like the path and method which should be documented.
"""
# print(f'resp:{resp}')
request = resp.action
#print(f'Type of request:{type(request)}')
#print(f'is recordnote? {isinstance(request, RecordNote)}')
#print(f'is HTTP request? {isinstance(request, HTTPRequest)}')
#print(f'is HTTP request? {type(request)}')
#print(f'is HTTP request? {type(request) == HTTPRequest}')
#print("same class?")
#print(request.__class__.__name__ == 'HTTPRequest')

if request.__class__.__name__ == 'RecordNote': # TODO check why isinstance does not work
# print(f'Type of request:{type(request)}')
# print(f'is recordnote? {isinstance(request, RecordNote)}')
# print(f'is HTTP request? {isinstance(request, HTTPRequest)}')
# print(f'is HTTP request? {type(request)}')
# print(f'is HTTP request? {type(request) == HTTPRequest}')
# print("same class?")
# print(request.__class__.__name__ == 'HTTPRequest')

if request.__class__.__name__ == 'RecordNote': # TODO check why isinstance does not work
self.check_openapi_spec(resp)
if request.__class__.__name__ == 'HTTPRequest':
path = request.path
Expand All @@ -67,21 +69,60 @@ def update_openapi_spec(self, resp):
if path not in self.openapi_spec['endpoints']:
self.openapi_spec['endpoints'][path] = {}
# Update the method description within the path
example, reference = self.parse_http_response_to_openapi_example(result, path)
self.openapi_spec['endpoints'][path][method.lower()] = {
"summary": f"{method} operation on {path}",
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {"type": "object"} # Simplified for example
"schema": {
"$ref": reference
},
"examples": example
}
}
}

}
}

def extract_response_example(self, html_content):
soup = BeautifulSoup(html_content, 'html.parser')

# Extract the JavaScript example code
example_code = soup.find('code', {'id': 'example'})
if example_code:
example_text = example_code.get_text()
else:
return None

# Extract the result placeholder for the response
result_code = soup.find('code', {'id': 'result'})
if result_code:
result_text = result_code.get_text()
else:
return None

# Format the response example
return json.loads(result_text)

def parse_http_response_to_openapi_example(self, http_response, path):
# Extract headers and body from the HTTP response
headers, body = http_response.split('\r\n\r\n', 1)

# Convert the JSON body to a Python dictionary
body_dict = json.loads(body)
reference = self.parse_http_response_to_schema(body_dict, path)

entry_dict = {}
# Build the OpenAPI response example
for entry in body_dict:
key = entry.get("title") or entry.get("name") or entry.get("id")
entry_dict[key] = {"value": entry}

return entry_dict, reference

def write_openapi_to_yaml(self):
"""Writes the updated OpenAPI specification to a YAML file with a timestamped filename."""
Expand All @@ -91,15 +132,15 @@ def write_openapi_to_yaml(self):
"openapi": self.openapi_spec["openapi"],
"info": self.openapi_spec["info"],
"servers": self.openapi_spec["servers"],
"components": self.openapi_spec["components"],
"paths": self.openapi_spec["endpoints"]
}


# Create directory if it doesn't exist and generate the timestamped filename
os.makedirs(self.file_path, exist_ok=True)

# Write to YAML file
with open(self.file , 'w') as yaml_file:
with open(self.file, 'w') as yaml_file:
yaml.dump(openapi_data, yaml_file, allow_unicode=True, default_flow_style=False)
print(f"OpenAPI specification written to {self.filename}.")
except Exception as e:
Expand Down Expand Up @@ -143,8 +184,6 @@ def read_yaml_to_string(self, filepath):
print(f"Error reading file {filepath}: {e}")
return None



def check_openapi_spec(self, note):
"""
Uses OpenAI's GPT model to generate a complete OpenAPI specification based on a natural language description.
Expand All @@ -157,9 +196,9 @@ def check_openapi_spec(self, note):
description = self.extract_description(note)

# Prepare the prompt for the LLM to generate the entire OpenAPI YAML
prompt =[{'role': 'system', 'content': f"Update the OpenAPI specification in YAML format based on the following description of an API and return only the OpenAPI specification as a yaml:" \
f"\n Description:{description},\n yaml:{self.read_yaml_to_string(self.file)}"}]

prompt = [{'role': 'system',
'content': f"Update the OpenAPI specification in YAML format based on the following description of an API and return only the OpenAPI specification as a yaml:" \
f"\n Description:{description},\n yaml:{self.read_yaml_to_string(self.file)}"}]

# Ask the model to generate the complete YAML specification
openai.api_key = self.api_key
Expand All @@ -179,16 +218,41 @@ def check_openapi_spec(self, note):
except Exception as e:
print(f"An error occurred: {e}")
raise Exception(e)
#response, completion = self.llm.instructor.chat.completions.create_with_completion(model=self.llm.model, messages=prompt, response_model=capabilities_to_action_model(self.capabilities))
# response, completion = self.llm.instructor.chat.completions.create_with_completion(model=self.llm.model, messages=prompt, response_model=capabilities_to_action_model(self.capabilities))

## Parse the model's response assuming it's a valid YAML
#print(f'new yaml file:{completion.choices[0].message}')
#new_openapi_spec = yaml.safe_load(completion.choices[0].message)
#
## Write the generated YAML back to file
#with open(self.file, 'w') as file:
# yaml.safe_dump(new_openapi_spec, file, default_flow_style=False, sort_keys=False)
## Parse the model's response assuming it's a valid YAML
# print(f'new yaml file:{completion.choices[0].message}')
# new_openapi_spec = yaml.safe_load(completion.choices[0].message)
#
## Write the generated YAML back to file
# with open(self.file, 'w') as file:
# yaml.safe_dump(new_openapi_spec, file, default_flow_style=False, sort_keys=False)

def extract_description(self, note):
return note.action.content

def parse_http_response_to_schema(self, body_dict, path):
# Create object name
object_name = path.split("/")[1].capitalize()
object_name = object_name[:len(object_name) - 1]

# Parse body dict to types
properties_dict = {}

for param in body_dict:
for key, value in param.items():
if key == "id":
properties_dict[key] = {"type": str(type(value).__name__), "format": "uuid", "example": str(value)}
else:
properties_dict[key] = {"type": str(type(value).__name__), "example": str(value)}
break

object_dict = {"type": "object", "properties": properties_dict}

if not object_name in self.openapi_spec["components"]["schemas"].keys():
self.openapi_spec["components"]["schemas"][object_name] = object_dict

schemas = self.openapi_spec["components"]["schemas"]
print(f'schemas: {schemas}')
reference = "#/components/schemas/" + object_name
return reference
Loading