diff --git a/main.go b/main.go index d47b1ebd..b1bdef4b 100644 --- a/main.go +++ b/main.go @@ -73,6 +73,10 @@ var mcpFile string //go:embed pkg/externalfunctions/rhsc.go var rhscFile string +//go:embed pkg/externalfunctions/fluent.go +var fluentFile string + + func init() { // initialize config config.InitConfig([]string{}, map[string]interface{}{ @@ -110,6 +114,7 @@ func main() { "auth": authFile, "mcp": mcpFile, "rhsc": rhscFile, + "fluent": fluentFile, } // Load function definitions diff --git a/pkg/externalfunctions/dataextraction.go b/pkg/externalfunctions/dataextraction.go index 4c5004a9..5ea61dce 100644 --- a/pkg/externalfunctions/dataextraction.go +++ b/pkg/externalfunctions/dataextraction.go @@ -761,6 +761,15 @@ func mapToSparseVec(m map[uint]float32) *qdrant.Vector { return qdrant.NewVectorSparse(keys, vals) } +// Helper function +func jsonMarshal(v interface{}) string { + bytes, err := json.Marshal(v) + if err != nil { + return "{}" + } + return string(bytes) +} + // StoreElementsInVectorDatabase stores elements in the vector database. // // Tags: @@ -798,6 +807,25 @@ func StoreElementsInVectorDatabase(elements []sharedtypes.CodeGenerationElement, // vectorElements := []codegeneration.VectorDatabaseElement{} points := make([]*qdrant.PointStruct, len(elements)) for i, element := range elements { + // Convert parameters to a map for easier querying + parametersMap := make([]interface{}, len(element.Parameters)) + for j, param := range element.Parameters { + parametersMap[j] = map[string]interface{}{ + "name": param.Name, + "type": param.Type, + "description": param.Description, + } + } + + // Convert example to a map + exampleMap := map[string]interface{}{ + "description": element.Example.Description, + "code": map[string]interface{}{ + "type": element.Example.Code.Type, + "text": element.Example.Code.Text, + }, + } + points[i] = &qdrant.PointStruct{ Id: qdrant.NewIDUUID(element.Guid.String()), Vectors: qdrant.NewVectorsMap(map[string]*qdrant.Vector{ @@ -808,8 +836,11 @@ func StoreElementsInVectorDatabase(elements []sharedtypes.CodeGenerationElement, "name": element.Name, "name_pseudocode": element.NamePseudocode, "name_formatted": element.NameFormatted, + "summary": element.Summary, "type": string(element.Type), "parent_class": strings.Join(element.Dependencies, "."), + "parameters": jsonMarshal(parametersMap), + "example": jsonMarshal(exampleMap), "metadata": element.VectorDBMetadata, }), } diff --git a/pkg/externalfunctions/externalfunctions.go b/pkg/externalfunctions/externalfunctions.go index dc2d79e3..5fe84bf4 100644 --- a/pkg/externalfunctions/externalfunctions.go +++ b/pkg/externalfunctions/externalfunctions.go @@ -102,6 +102,7 @@ var ExternalFunctionsMap = map[string]interface{}{ "JsonPath": JsonPath, "StringConcat": StringConcat, "StringFormat": StringFormat, + "FluentCodeGenTest": FluentCodeGenTest, // code generation "LoadCodeGenerationElements": LoadCodeGenerationElements, @@ -188,4 +189,7 @@ var ExternalFunctionsMap = map[string]interface{}{ // rhsc "SetCopilotGenerateRequestJsonBody": SetCopilotGenerateRequestJsonBody, + + // fluent + "FluentCodeGen": FluentCodeGen, } diff --git a/pkg/externalfunctions/fluent.go b/pkg/externalfunctions/fluent.go new file mode 100644 index 00000000..c20e953a --- /dev/null +++ b/pkg/externalfunctions/fluent.go @@ -0,0 +1,105 @@ +// Copyright (C) 2025 ANSYS, Inc. and/or its affiliates. +// SPDX-License-Identifier: MIT +// +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package externalfunctions + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" +) + +// FluentCodeGen sends a raw user message to the Fluent container and returns the response +// +// Tags: +// - @displayName: Fluent Code Gen +// +// Parameters: +// - message: the raw user message to send to the container +// +// Returns: +// - response: the response from the Fluent container as a string +func FluentCodeGen(message string) (response string) { + url := "http://aali-fluent:8000/chat" + + // Create the JSON payload directly + jsonData := fmt.Sprintf(`{"message": "%s"}`, message) + + // Create HTTP request + req, err := http.NewRequest("POST", url, bytes.NewBufferString(jsonData)) + if err != nil { + panic(fmt.Sprintf("Error creating HTTP request: %v", err)) + } + + // Set headers + req.Header.Set("Accept", "application/json") + req.Header.Set("Content-Type", "application/json") + + // Execute the request + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + panic(fmt.Sprintf("Error executing HTTP request: %v", err)) + } + defer resp.Body.Close() + + // Read the response body + body, err := io.ReadAll(resp.Body) + if err != nil { + panic(fmt.Sprintf("Error reading response body: %v", err)) + } + + // Check if the response code is successful (2xx) + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + panic(fmt.Sprintf("HTTP request failed with status code %d: %s", resp.StatusCode, string(body))) + } + + // Parse JSON response to extract just the response content + var responseData map[string]interface{} + if err := json.Unmarshal(body, &responseData); err != nil { + panic(fmt.Sprintf("Error parsing JSON response: %v", err)) + } + + // Extract the response field + if responseField, exists := responseData["response"]; exists { + if responseArray, ok := responseField.([]interface{}); ok && len(responseArray) > 0 { + // Concatenate all items in the response array with a newline + var concatenatedResponse string + for _, item := range responseArray { + if str, ok := item.(string); ok { + concatenatedResponse += str + "\n" + } else { + concatenatedResponse += fmt.Sprintf("%v\n", item) + } + } + // Remove the trailing newline + concatenatedResponse = strings.TrimRight(concatenatedResponse, "\n") + return "```python\n" + concatenatedResponse + "```" + } + } + + // Fallback to raw response if parsing fails + return string(body) +} diff --git a/pkg/externalfunctions/generic.go b/pkg/externalfunctions/generic.go index cf31a045..93232d68 100644 --- a/pkg/externalfunctions/generic.go +++ b/pkg/externalfunctions/generic.go @@ -270,3 +270,77 @@ func StringFormat(data any, format string) string { } return fmt.Sprintf(format, data) } + +// FluentCodeGen sends a raw user message to the Fluent container and returns the response. +// It takes a user message and posts it to the Fluent API endpoint to generate code. +// +// Tags: +// - @displayName: Fluent Code Gen Test +// +// Parameters: +// - message: the raw user message to send to the container +// +// Returns: +// - response: the response from the Fluent container as a string +func FluentCodeGenTest(message string) (response string) { + url := "http://aali-fluent:8000/chat" + + // Create the JSON payload directly + jsonData := fmt.Sprintf(`{"message": "%s"}`, message) + + // Create HTTP request + req, err := http.NewRequest("POST", url, bytes.NewBufferString(jsonData)) + if err != nil { + panic(fmt.Sprintf("Error creating HTTP request: %v", err)) + } + + // Set headers + req.Header.Set("Accept", "application/json") + req.Header.Set("Content-Type", "application/json") + + // Execute the request + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + panic(fmt.Sprintf("Error executing HTTP request: %v", err)) + } + defer resp.Body.Close() + + // Read the response body + body, err := io.ReadAll(resp.Body) + if err != nil { + panic(fmt.Sprintf("Error reading response body: %v", err)) + } + + // Check if the response code is successful (2xx) + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + panic(fmt.Sprintf("HTTP request failed with status code %d: %s", resp.StatusCode, string(body))) + } + + // Parse JSON response to extract just the response content + var responseData map[string]interface{} + if err := json.Unmarshal(body, &responseData); err != nil { + panic(fmt.Sprintf("Error parsing JSON response: %v", err)) + } + + // Extract the response field + if responseField, exists := responseData["response"]; exists { + if responseArray, ok := responseField.([]interface{}); ok && len(responseArray) > 0 { + // Concatenate all items in the response array with a newline + var concatenatedResponse string + for _, item := range responseArray { + if str, ok := item.(string); ok { + concatenatedResponse += str + "\n" + } else { + concatenatedResponse += fmt.Sprintf("%v\n", item) + } + } + // Remove the trailing newline + concatenatedResponse = strings.TrimRight(concatenatedResponse, "\n") + return "```python\n" + concatenatedResponse + "```" + } + } + + // Fallback to raw response if parsing fails + return string(body) +} diff --git a/pkg/externalfunctions/privatefunctions.go b/pkg/externalfunctions/privatefunctions.go index 755eab4e..daaeae59 100644 --- a/pkg/externalfunctions/privatefunctions.go +++ b/pkg/externalfunctions/privatefunctions.go @@ -2044,7 +2044,16 @@ func codeGenerationProcessHybridSearchEmbeddings(elements []sharedtypes.CodeGene batchData := elements[i:end] batchTextToEmbed := make([]string, len(batchData)) for j, data := range batchData { - batchTextToEmbed[j] = data.NameFormatted + "\n" + data.NamePseudocode + "\n" + data.Summary + "\n" + strings.Join(data.Dependencies, " ") + "\n" + strings.Join(data.Dependencies, ".") + // Build parameter text + paramText := "" + for _, param := range data.Parameters { + paramText += param.Name + " " + param.Type + " " + param.Description + " " + } + + // Build example text + exampleText := data.Example.Description + " " + data.Example.Code.Text + + batchTextToEmbed[j] = data.NameFormatted + "\n" + data.NamePseudocode + "\n" + data.Summary + "\n" + strings.Join(data.Dependencies, " ") + "\n" + strings.Join(data.Dependencies, ".") + "\n" + paramText + "\n" + exampleText } // Send http request