llm/go-client/cmd/client.go (188 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package main import ( "bufio" "context" "fmt" "os" "strings" ) import ( "dubbo.apache.org/dubbo-go/v3" _ "dubbo.apache.org/dubbo-go/v3/imports" "dubbo.apache.org/dubbo-go/v3/registry" ) import ( "github.com/apache/dubbo-go-samples/llm/config" chat "github.com/apache/dubbo-go-samples/llm/proto" ) type ChatContext struct { ID string History []*chat.ChatMessage } var ( contexts = make(map[string]*ChatContext) currentCtxID string contextOrder []string maxID uint8 = 0 availableModels []string currentModel string maxContextCount int ) func handleCommand(cmd string) (resp string) { cmd = strings.TrimSpace(cmd) resp = "" switch { case cmd == "/?" || cmd == "/help": resp += "Available commands:\n" resp += "/? help - Show this help\n" resp += "/list - List all contexts\n" resp += "/cd <context> - Switch context\n" resp += "/new - Create new context\n" resp += "/models - List available models\n" resp += "/model <name> - Switch to specified model" return resp case cmd == "/list": fmt.Printf("Stored contexts (max %d):\n", maxContextCount) for _, ctxID := range contextOrder { resp += fmt.Sprintf("- %s\n", ctxID) } resp = strings.TrimSuffix(resp, "\n") return resp case strings.HasPrefix(cmd, "/cd "): target := strings.TrimPrefix(cmd, "/cd ") if ctx, exists := contexts[target]; exists { currentCtxID = ctx.ID resp += fmt.Sprintf("Switched to context: %s", target) } else { resp += "Context not found" } return resp case cmd == "/new": newID := createContext() currentCtxID = newID resp += fmt.Sprintf("Created new context: %s", newID) return resp case cmd == "/models": resp += "Available models:" for _, model := range availableModels { marker := " " if model == currentModel { marker = "*" } resp += fmt.Sprintf("\n%s %s", marker, model) } return resp case strings.HasPrefix(cmd, "/model "): modelName := strings.TrimPrefix(cmd, "/model ") modelFound := false for _, model := range availableModels { if model == modelName { currentModel = model modelFound = true break } } if modelFound { resp += fmt.Sprintf("Switched to model: %s", modelName) } else { resp += fmt.Sprintf("Model '%s' not found. Use /models to see available models.", modelName) } return resp default: return "Invalid command, use /? for help" } } func createContext() string { id := fmt.Sprintf("ctx%d", maxID) maxID++ contexts[id] = &ChatContext{ ID: id, History: []*chat.ChatMessage{}, } contextOrder = append(contextOrder, id) // Use configurable max context count if len(contextOrder) > maxContextCount { delete(contexts, contextOrder[0]) contextOrder = contextOrder[1:] } return id } func main() { cfg, err := config.GetConfig() if err != nil { fmt.Printf("Error loading config: %v\n", err) return } availableModels = cfg.OllamaModels currentModel = cfg.DefaultModel() maxContextCount = cfg.MaxContextCount currentCtxID = createContext() ins, err := dubbo.NewInstance( dubbo.WithRegistry( registry.WithNacos(), registry.WithAddress(cfg.NacosURL), ), ) if err != nil { panic(err) } // configure the params that only client layer cares cli, err := ins.NewClient() if err != nil { panic(err) } svc, err := chat.NewChatService(cli) if err != nil { fmt.Printf("Error creating service: %v\n", err) return } fmt.Printf("\nSend a message (/? for help) - Using model: %s\n", currentModel) scanner := bufio.NewScanner(os.Stdin) for { fmt.Print("\n> ") scanner.Scan() input := scanner.Text() // handle command if strings.HasPrefix(input, "/") { fmt.Println(handleCommand(input)) continue } func() { currentCtx := contexts[currentCtxID] currentCtx.History = append(currentCtx.History, &chat.ChatMessage{ Role: "human", Content: input, Bin: nil, }) stream, err := svc.Chat(context.Background(), &chat.ChatRequest{ Messages: currentCtx.History, Model: currentModel, }) if err != nil { panic(err) } defer func(stream chat.ChatService_ChatClient) { err := stream.Close() if err != nil { fmt.Printf("Error closing stream: %v\n", err) } }(stream) resp := "" for stream.Recv() { msg := stream.Msg() c := msg.Content resp += c fmt.Print(c) } fmt.Print("\n") if err := stream.Err(); err != nil { fmt.Printf("Stream error: %v\n", err) return } currentCtx.History = append(currentCtx.History, &chat.ChatMessage{ Role: "ai", Content: resp, Bin: nil, }) }() } }