Skip to content

Commit

Permalink
feat: add ai feature to cfctl - not completed
Browse files Browse the repository at this point in the history
Signed-off-by: Youngjin Jo <[email protected]>
  • Loading branch information
yjinjo committed Nov 12, 2024
1 parent 4164da4 commit ccaf9d3
Show file tree
Hide file tree
Showing 2 changed files with 221 additions and 11 deletions.
115 changes: 104 additions & 11 deletions cmd/ai.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
Expand All @@ -16,8 +17,9 @@ import (
)

var (
apiToken string
configPath = filepath.Join(os.Getenv("HOME"), ".spaceone", "config")
apiToken string
configPath = filepath.Join(os.Getenv("HOME"), ".spaceone", "config")
resourceDir = filepath.Join(os.Getenv("HOME"), ".spaceone", "training_data") // 학습 전용 디렉터리 경로
)

// aiCmd represents the ai command
Expand Down Expand Up @@ -68,15 +70,46 @@ var aiConfigCmd = &cobra.Command{
},
}

// aiChatCmd represents the ai chat command
var aiChatCmd = &cobra.Command{
Use: "chat",
Short: "Ask questions about project resources",
Run: func(cmd *cobra.Command, args []string) {
// Use the query flag instead of args[0]
chat, err := cmd.Flags().GetString("query")
if err != nil || chat == "" {
pterm.Error.Println("Please provide a query with the -q flag.")
return
}

// Load resources context from directory
contextData, err := loadAPIResourcesContext(resourceDir)
if err != nil {
pterm.Error.Println("Failed to load resources context:", err)
return
}

// Call AI function with query and context
result, err := queryAIWithContext(chat, contextData)
if err != nil {
pterm.Error.Println("Error querying AI:", err)
return
}

pterm.Info.Println("AI Response:", result)
},
}

// runAIWithOpenAI processes input with OpenAI's streaming API
func runAIWithOpenAI(input string, natural bool) (string, error) {
// Load the API key from config if it's not set in the environment
apiToken = os.Getenv("OPENAI_API_TOKEN")
if apiToken == "" {
apiToken, _ = readAPIKeyFromConfig()
}
if apiToken == "" {
return "", errors.New("OpenAI API key is not set. Run `cfctl ai config` to configure it.")
var err error
apiToken, err = readAPIKeyFromConfig()
if err != nil {
return "", errors.New("OpenAI API key is not set. Run `cfctl ai config` to configure it.")
}
}

client := openai.NewClient(apiToken)
Expand Down Expand Up @@ -132,7 +165,7 @@ func saveAPIKeyToConfig(apiKey string) error {

// Check if OPENAI_SECRET_KEY is already present
if strings.Contains(content, "OPENAI_SECRET_KEY=") {
// Update the existing key
// Update the existing key without adding an extra `=` character
content = strings.ReplaceAll(content,
"OPENAI_SECRET_KEY="+getAPIKeyFromContent(content),
"OPENAI_SECRET_KEY="+apiKey)
Expand All @@ -157,29 +190,89 @@ func getAPIKeyFromContent(content string) string {
return ""
}

// readAPIKeyFromConfig reads the OpenAI API key from the config file
// readAPIKeyFromConfig reads the OpenAI API key from the config file and sets it to apiToken
func readAPIKeyFromConfig() (string, error) {
file, err := os.Open(configPath)
if err != nil {
return "", err
return "", fmt.Errorf("failed to open config file: %v", err)
}
defer file.Close()

scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if len(line) > 0 && line[0:17] == "OPENAI_SECRET_KEY=" {
return line[17:], nil
line = strings.TrimSpace(line) // 공백 제거
if strings.HasPrefix(line, "OPENAI_SECRET_KEY=") {
apiToken = strings.TrimPrefix(line, "OPENAI_SECRET_KEY=")
apiToken = strings.TrimSpace(apiToken)
return apiToken, nil
}
}
return "", errors.New("API key not found in config file")
}

// loadAPIResourcesContext loads all files in the given directory and concatenates their content as context
func loadAPIResourcesContext(dirPath string) (string, error) {
files, err := ioutil.ReadDir(dirPath)
if err != nil {
return "", fmt.Errorf("failed to read resources directory: %v", err)
}

var contentBuilder strings.Builder
for _, file := range files {
if !file.IsDir() {
filePath := filepath.Join(dirPath, file.Name())
data, err := ioutil.ReadFile(filePath)
if err != nil {
return "", fmt.Errorf("failed to read file %s: %v", filePath, err)
}
contentBuilder.WriteString(string(data) + "\n")
}
}

return contentBuilder.String(), nil
}

// queryAIWithContext queries the OpenAI API with a specific context and user query
func queryAIWithContext(query, contextData string) (string, error) {
apiToken = os.Getenv("OPENAI_API_TOKEN")
if apiToken == "" {
apiToken, _ = readAPIKeyFromConfig()
}
if apiToken == "" {
return "", errors.New("OpenAI API token is not set. Run `cfctl ai config` to configure it.")
}

client := openai.NewClient(apiToken)
ctx := context.Background()

// Prompt with context for AI model
prompt := fmt.Sprintf("Context: %s\n\nQuestion: %s\nAnswer:", contextData, query)

req := openai.CompletionRequest{
Model: openai.GPT3Babbage002,
MaxTokens: 5, // Adjust as needed
Prompt: prompt,
Stream: false,
}

resp, err := client.CreateCompletion(ctx, req)
if err != nil {
return "", fmt.Errorf("AI query error: %v", err)
}

// Return the AI's response text
return strings.TrimSpace(resp.Choices[0].Text), nil
}

func init() {
rootCmd.AddCommand(aiCmd)
aiCmd.Flags().String("input", "", "Input text for the AI to process")
aiCmd.Flags().BoolP("natural", "n", false, "Enable natural language mode for the AI")
aiChatCmd.Flags().StringP("query", "q", "", "Query text for the AI to process")
aiChatCmd.MarkFlagRequired("query")

// Add config command as a subcommand to aiCmd
aiCmd.AddCommand(aiConfigCmd)
aiCmd.AddCommand(aiChatCmd)
}
117 changes: 117 additions & 0 deletions training/api-resources.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
Service | Resource | Short Names | Verb
board | Health | | Check, Watch
board | Post | | create, update, send, delete, get, list, stat
board | ServerInfo | | get_version
config | Health | | Check, Watch
config | ServerInfo | | get_version
config | UserConfig | | create, update, set, delete, get, list, stat
config | PublicConfig | | create, update, delete, get, list, stat
config | DomainConfig | | create, update, set, delete, get, list, stat
cost_analysis | ServerInfo | | get_version
cost_analysis | Job | | cancel, get, list, stat
cost_analysis | Cost | | create, delete, get, list, analyze, stat
cost_analysis | Budget | | create, update, set_notification, delete, get, list, stat
cost_analysis | Health | | Check, Watch
cost_analysis | CostQuerySet | | create, update, delete, get, list, stat
cost_analysis | CostReport | | send, get_url, get, list, stat
cost_analysis | CostReportConfig | | create, update, update_recipients, enable, disable, delete, run, get, list, stat
cost_analysis | CostReportData | | list, analyze, stat
cost_analysis | DataSource | | register, update, update_permissions, update_plugin, update_secret_data, verify_plugin, enable, disable, deregister, sync, get, list, stat
cost_analysis | DataSourceAccount | | update, reset, get, list, analyze, stat
cost_analysis | BudgetUsage | | list, analyze, stat
cost_analysis | DataSourceRule | | create, update, change_order, delete, get, list, stat
cost_analysis | UnifiedCost | | get, list, analyze, stat
cost_analysis | JobTask | | get, list, stat
dashboard | PrivateFolder | | create, update, delete, get, list, stat
dashboard | PublicWidget | | create, update, delete, load, get, list
dashboard | PublicFolder | | create, update, share, unshare, delete, get, list, stat
dashboard | PublicDataTable | | add, transform, update, delete, load, get, list
dashboard | PublicDashboard | | create, update, change_folder, share, unshare, delete, get, list, stat
dashboard | PrivateWidget | | create, update, delete, load, get, list
dashboard | Health | | Check, Watch
dashboard | PrivateDataTable | | add, transform, update, delete, load, get, list
dashboard | PrivateDashboard | | create, update, change_folder, delete, get, list, stat
dashboard | ServerInfo | | get_version
file_manager | Health | | Check, Watch
file_manager | File | | add, update, delete, get_download_url, get, list, stat
file_manager | ServerInfo | | get_version
identity | RoleBinding | | create, update_role, delete, get, list, stat
identity | ServerInfo | | get_version
identity | Agent | | create, enable, disable, regenerate, delete, get, list
identity | App | | create, update, generate_client_secret, enable, disable, delete, get, check, list, stat
identity | Domain | | create, update, delete, enable, disable, get, get_auth_info, get_public_key, list, stat
identity | Endpoint | | list
identity | ExternalAuth | | set, unset, get
identity | Job | | delete, get, list, stat
identity | Project | | create, update, update_project_type, change_project_group, delete, add_users, remove_users, get, list, stat
identity | ProjectGroup | | create, update, change_parent_group, delete, add_users, remove_users, get, list, stat
identity | Health | | Check, Watch
identity | Provider | | create, update, update_plugin, delete, get, list, stat
identity | Role | | create, update, enable, disable, delete, get, list, list_basic_role, stat
identity | System | | init
identity | Schema | | create, update, delete, get, list, stat
identity | ServiceAccount | | create, update, update_secret_data, delete_secret_data, delete, get, list, stat
identity | Token | it | issue, grant
identity | TrustedAccount | | create, update, update_secret_data, delete, sync, get, list, stat
identity | User | iu | create, update, verify_email, disable_mfa, set_required_actions, set_refresh_timeout, enable, disable, delete, get, list, stat
identity | UserGroup | | create, update, delete, add_users, remove_users, get, list, stat
identity | UserProfile | | update, verify_email, confirm_email, reset_password, enable_mfa, disable_mfa, confirm_mfa, get, get_workspaces, get_workspace_groups
identity | Workspace | | create, update, change_workspace_group, delete, enable, disable, get, check, list, stat
identity | WorkspaceGroup | | create, update, delete, add_users, remove_users, update_role, get, list, stat
identity | WorkspaceGroupUser | | add, remove, update_role, find, get, list, stat
identity | WorkspaceUser | | create, get, find, list, stat
inventory | MetricExample | | create, update, delete, get, list, stat
inventory | Note | | create, update, delete, get, list, stat
inventory | Region | | create, update, delete, get, list, stat
inventory | MetricData | | list, stat, analyze
inventory | Namespace | | create, update, delete, get, list, stat
inventory | Metric | | create, update, delete, run, test, get, list, stat
inventory | JobTask | | delete, get, list, stat
inventory | Job | | delete, get, list, analyze, stat
inventory | CollectorRule | | create, update, change_order, delete, get, list, stat
inventory | Collector | | create, update, update_plugin, verify_plugin, delete, get, list, stat, collect
inventory | CloudServiceType | | create, update, delete, get, list, stat
inventory | CloudServiceStats | | list, analyze, stat
inventory | Health | | Check, Watch
inventory | ServerInfo | | get_version
inventory | ChangeHistory | | list, stat
inventory | CloudService | | create, update, delete, get, list, export, analyze, stat
inventory | CloudServiceQuerySet | | create, update, delete, run, test, enable, disable, get, list, stat
inventory | CloudServiceReport | | create, update, delete, send, get, list, stat
monitoring | Health | | Check, Watch
monitoring | DataSource | | register, update, enable, disable, deregister, update_plugin, verify_plugin, get, list, stat
monitoring | EscalationPolicy | | create, update, set_default, delete, get, list, stat
monitoring | Event | | create, get, list, stat
monitoring | EventRule | | create, update, change_order, delete, get, list, stat
monitoring | Log | | list
monitoring | Metric | | list, get_data
monitoring | ServerInfo | | get_version
monitoring | Alert | | create, update, assign_user, update_state, delete, get, list, stat
monitoring | Note | | create, update, delete, get, list, stat
monitoring | ProjectAlertConfig | | create, update, delete, get, list, stat
monitoring | Webhook | | create, update, update_plugin, verify_plugin, enable, disable, delete, get, list, stat
notification | Health | | Check, Watch
notification | Protocol | | create, update, update_plugin, enable, disable, delete, get, list, stat
notification | ProjectChannel | | create, update, set_schedule, set_subscription, enable, disable, delete, get, list, stat
notification | Notification | | create, push, delete, set_read, get, list, stat
notification | ServerInfo | | get_version
notification | UserChannel | | create, update, set_schedule, set_subscription, enable, disable, delete, get, list, stat
plugin | Supervisor | | publish, register, update, deregister, enable, disable, recover_plugin, get, list, stat, list_plugins
plugin | Health | | Check, Watch
plugin | ServerInfo | | get_version
plugin | Plugin | | get_plugin_endpoint, get_plugin_metadata, notify_failure
repository | Health | | Check, Watch
repository | ServerInfo | | get_version
repository | Plugin | rp | register, update, deregister, enable, disable, get_versions, get, list
repository | DashboardTemplate | | register, update, deregister, enable, disable, get, list
repository | Repository | | list
secret | TrustedSecret | | create, update, delete, update_data, get_data, get, list, stat
secret | Health | | Check, Watch
secret | ServerInfo | | get_version
secret | Secret | | create, update, delete, enable, disable, update_data, get_data, get, list, stat
secret | UserSecret | | create, update, delete, update_data, get_data, get, list, stat
statistics | Schedule | | add, update, enable, disable, delete, get, list, stat
statistics | Health | | Check, Watch
statistics | ServerInfo | | get_version
statistics | History | | create, list, stat
statistics | Resource | | stat

0 comments on commit ccaf9d3

Please sign in to comment.