Skip to content

Commit

Permalink
Added openapi3 automatic updates to apps that have a missing body field
Browse files Browse the repository at this point in the history
  • Loading branch information
frikky committed Apr 16, 2024
1 parent 342ed08 commit 5a7f0a2
Show file tree
Hide file tree
Showing 2 changed files with 304 additions and 34 deletions.
225 changes: 201 additions & 24 deletions kms.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"encoding/base64"

"github.com/frikky/schemaless"
"github.com/frikky/kin-openapi/openapi3"
openai "github.com/sashabaranov/go-openai"
)

Expand Down Expand Up @@ -813,55 +814,231 @@ func getBadOutputString(action Action, appname, inputdata, outputBody string, st
return outputData
}

// Ask itself for information about the API in case it has it
// FIXMe: Add internet to search for the relevant API as well
func getOpenApiInformation(appname, action string) string {
openaiClient := openai.NewClient(os.Getenv("OPENAI_API_KEY"))
func RunApiQuery(systemMessage, userMessage string) (string, error) {
//log.Printf("[INFO] System message (find API documentation): %s", systemMessage)
cnt := 0
openaiClient := openai.NewClient(os.Getenv("OPENAI_API_KEY"))

action = GetCorrectActionName(action)
chatCompletion := openai.ChatCompletionRequest{
Model: model,
Messages: []openai.ChatCompletionMessage{},
Temperature: 0.8,
MaxTokens: 200,
}
if len(systemMessage) > 0 {
chatCompletion.Messages = append(chatCompletion.Messages, openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleSystem,
Content: systemMessage,
})
}

systemMessage := fmt.Sprintf("Output a valid JSON body format for a HTTP request %s in the %s API?", action, appname)
if len(userMessage) > 0 {
chatCompletion.Messages = append(chatCompletion.Messages, openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleUser,
Content: userMessage,
})
}

//log.Printf("[INFO] System message (find API documentation): %s", systemMessage)
if len(chatCompletion.Messages) == 0 {
return "", errors.New("No messages to send to OpenAI. Pass systemmessage, usermessage")
}

contentOutput := ""
for {
if cnt >= 3 {
log.Printf("[ERROR] Failed to match JSON in runActionAI after 5 tries for openapi info")

return ""
return "", errors.New("Failed to match JSON in runActionAI after 5 tries for openapi info")
}

openaiResp2, err := openaiClient.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
Model: model,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleSystem,
Content: systemMessage,
},
},
Temperature: 0.4,
MaxTokens: 100,
},
chatCompletion,
)

if err != nil {
log.Printf("[ERROR] Failed to create chat completion in get api info. Retrying in 3 seconds (4): %s", err)
log.Printf("[ERROR] Failed to create chat completion for api info. Retrying in 3 seconds (4): %s", err)
time.Sleep(3 * time.Second)
cnt += 1
continue
}

contentOutput = openaiResp2.Choices[0].Message.Content
if strings.Contains(contentOutput, "success\": false") {
return ""
}

break
}

return contentOutput, nil
}

// Ask itself for information about the API in case it has it
// FIXMe: Add internet to search for the relevant API as well
func getOpenApiInformation(appname, action string) string {
var err error
var contentOutput string
action = GetCorrectActionName(action)

systemMessage := fmt.Sprintf("Output a valid JSON body format for a HTTP request %s in the %s API?", action, appname)

//log.Printf("[INFO] System message (find API documentation): %s", systemMessage)
contentOutput, err = RunApiQuery(systemMessage, "")
if err != nil {
log.Printf("[ERROR] Failed to run API query: %s", err)
}

if strings.Contains(contentOutput, "success\": false") {
return ""
}

return contentOutput
}

func UpdateActionBody(action WorkflowAppAction) (string, error) {
currentParam := "body"
if len(action.Name) == 0 {
return "", errors.New("No action name found")
}

if len(action.AppName) == 0 {
return "", errors.New("No app name found")
}

newName := strings.Replace(strings.Title(GetCorrectActionName(action.Name)), " ", "_", -1)

systemMessage := fmt.Sprintf("Output a valid HTTP body to %s in %s. Output ONLY JSON without explainers.", newName, action.AppName)
userMessage := ""

log.Printf("\n\nBODY CREATE SYSTEM MESSAGE: %s\n\n", systemMessage)

contentOutput, err := RunApiQuery(systemMessage, userMessage)
if err != nil {
log.Printf("[ERROR] Failed to run API query: %s", err)
return "", err
}

if strings.Contains(contentOutput, "```json") {
start := strings.Index(contentOutput, "```json")
end := strings.Index(contentOutput, "```")
if start != -1 {
end = strings.Index(contentOutput[start+8:], "```")
}

log.Printf("[INFO] Start: %d, end: %d", start, end)

if start != -1 && end != -1 {
contentOutput = contentOutput[start+7 : end+7]
}
}

if strings.Contains(contentOutput, "```") {
start := strings.Index(contentOutput, "```")
end := strings.Index(contentOutput[start+3:], "```")
if start != -1 {
end = strings.Index(contentOutput[start+3:], "```")
}

if start != -1 && end != -1 {
contentOutput = contentOutput[start+3 : end+3]
}
}

output := map[string]interface{}{}
err = json.Unmarshal([]byte(contentOutput), &output)
if err != nil {
log.Printf("[ERROR] Failed to unmarshal JSON in get action body for find http endpoint (8): %s", err)
return "", errors.New("Failed to find JSON in output 2")
} else {
// Should save as new backup for the field?
// 1. Find the app
// 2. Find the action
// 3. Save the body as a backup for the action

ctx := context.Background()
app, err := GetApp(ctx, action.AppID, User{}, false)
if err != nil {
log.Printf("[ERROR] Failed to get app in get action body for find http endpoint (9): %s", err)
return contentOutput, nil
}

for _, foundAction := range app.Actions {
if foundAction.Name != action.Name {
continue
}

log.Printf("[INFO] Found action %s in app %s", foundAction.Name, app.Name)
for _, param := range foundAction.Parameters {
if param.Name != currentParam {
continue
}

if len(param.Value) > 0 && len(param.Example) > 0 {
return contentOutput, nil
}

log.Printf("\n\n[INFO] Found body param %s in action %s in app %s. Setting action example.\n\n", param.Name, foundAction.Name, app.Name)

param.Example = contentOutput
param.Tags = []string{"Generated"}

//go SetWorkflowAppDatastore(ctx, *app, app.ID)

openapiWrapper, err := GetOpenApiDatastore(ctx, app.ID)
if err != nil {
log.Printf("[ERROR] Failed to get openapi datastore in get action body for find http endpoint (10): %s", err)
return contentOutput, nil
}

// Update openapi with new body

swaggerLoader := openapi3.NewSwaggerLoader()
swaggerLoader.IsExternalRefsAllowed = true
openapi, err := swaggerLoader.LoadSwaggerFromData([]byte(openapiWrapper.Body))
if err != nil {
log.Printf("[ERROR] Failed to unmarshal openapi in get action body for find http endpoint (11): %s", err)
return contentOutput, nil
}

// Find the path
actionName := GetCorrectActionName(foundAction.Name)
updated := false
for _, pathItem := range openapi.Paths {
// Loop all path operations WITHOUT []string{method} and GetOperaiton().
for _, method := range []string{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "TRACE", "CONNECT"} {
operation := pathItem.GetOperation(method)
if operation == nil {
continue
}

correctName := strings.Replace(strings.ToLower(GetCorrectActionName(operation.Summary)), " ", "_", -1)
if correctName != actionName {
//log.Printf("[INFO] Skipping method %s with summary '%s' as it doesn't match action '%s'", method, correctName, actionName)
continue
}

// RequestBody.Body.Example
log.Printf("[INFO] Found method %s for action %s", method, action.Name)

// Should set updated IF we find the correct operation
updated = true
if operation.RequestBody != nil {
// Check if requestBody.content.example.example exists. Do NOT check application/json
if len(operation.RequestBody.Value.Content) > 0 {
}
}

}

if updated {
break
}
}

if updated {
log.Printf("[INFO] Updated openapi with new body for action %s in app %s", action.Name, app.Name)
break
}
}
}
}

return contentOutput, nil
}
Loading

0 comments on commit 5a7f0a2

Please sign in to comment.