diff --git a/neo/assistant/api.go b/neo/assistant/api.go index 59a00e44..94175a8c 100644 --- a/neo/assistant/api.go +++ b/neo/assistant/api.go @@ -10,7 +10,7 @@ import ( "github.com/gin-gonic/gin" jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou/fs" - "github.com/yaoapp/kun/utils" + "github.com/yaoapp/kun/log" chatctx "github.com/yaoapp/yao/neo/context" chatMessage "github.com/yaoapp/yao/neo/message" ) @@ -190,6 +190,14 @@ func (next *NextAction) Execute(c *gin.Context, ctx chatctx.Context, contents *c return fmt.Errorf("input is required") } + // Retry mode + retry := false + _, has = next.Payload["retry"] + if has { + retry = next.Payload["retry"].(bool) + ctx.Retry = retry + } + switch v := next.Payload["input"].(type) { case string: messages := chatMessage.Message{} @@ -338,6 +346,10 @@ func (ast *Assistant) streamChat( return 1 // continue } + // Retry mode + msg.Retry = ctx.Retry // Retry mode + msg.Silent = ctx.Silent // Silent mode + // Handle error if msg.Type == "error" { value := msg.String() @@ -348,7 +360,10 @@ func (ast *Assistant) streamChat( value = res.Error } } - chatMessage.New().Error(value).Done().Write(c.Writer) + newMsg := chatMessage.New().Error(value).Done() + newMsg.Retry = ctx.Retry + newMsg.Silent = ctx.Silent + newMsg.Write(c.Writer) return 0 // break } @@ -468,6 +483,9 @@ func (ast *Assistant) streamChat( "delta": true, }) + output.Retry = ctx.Retry // Retry mode + output.Silent = ctx.Silent // Silent mode + if isFirst { output.Assistant(ast.ID, ast.Name, ast.Avatar) isFirst = false @@ -489,6 +507,8 @@ func (ast *Assistant) streamChat( "type": "text", "delta": true, "done": true, + "retry": ctx.Retry, + "silent": ctx.Silent, }). Write(c.Writer) } @@ -521,6 +541,8 @@ func (ast *Assistant) streamChat( output := chatMessage.New().Done() if res != nil && res.Output != nil { output = chatMessage.New().Map(map[string]interface{}{"text": res.Output, "done": true}) + output.Retry = ctx.Retry + output.Silent = ctx.Silent } output.Write(c.Writer) done <- true @@ -542,6 +564,8 @@ func (ast *Assistant) streamChat( if err != nil { return fmt.Errorf("error: %s", err.Error()) } + msg.Retry = ctx.Retry + msg.Silent = ctx.Silent msg.Done().Write(c.Writer) } @@ -826,9 +850,10 @@ func (ast *Assistant) requestMessages(ctx context.Context, messages []chatMessag // For debug environment, print the request messages if os.Getenv("YAO_AGENT_PRINT_REQUEST_MESSAGES") == "true" { - fmt.Println("--- REQUEST_MESSAGES -----------------------------") - utils.Dump(newMessages) - fmt.Println("--- END REQUEST_MESSAGES -----------------------------") + for _, message := range newMessages { + raw, _ := jsoniter.MarshalToString(message) + log.Trace("[Request Message] %s", raw) + } } return newMessages, nil diff --git a/neo/assistant/hooks.go b/neo/assistant/hooks.go index ad61278b..754df070 100644 --- a/neo/assistant/hooks.go +++ b/neo/assistant/hooks.go @@ -8,8 +8,10 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/google/uuid" jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou/runtime/v8/bridge" + "github.com/yaoapp/kun/log" chatctx "github.com/yaoapp/yao/neo/context" "github.com/yaoapp/yao/neo/message" chatMessage "github.com/yaoapp/yao/neo/message" @@ -168,9 +170,7 @@ func (ast *Assistant) HookDone(c *gin.Context, context chatctx.Context, input [] text = content[:endIndex] text = strings.TrimSpace(text) if os.Getenv("YAO_AGENT_PRINT_TOOL_CALL") == "true" { - fmt.Println("---- EXTRACTED TOOL CALL ----") - fmt.Println(text) - fmt.Println("---- END EXTRACTED TOOL CALL ----") + log.Trace("[TOOL CALL] %s", text) } } } @@ -309,7 +309,81 @@ func (ast *Assistant) call(ctx context.Context, method string, c *gin.Context, c defer scriptCtx.Close() // Add sendMessage function to the script context - scriptCtx.WithFunction("SendMessage", func(info *v8go.FunctionCallbackInfo) *v8go.Value { + scriptCtx.WithFunction("SendMessage", sendMessage(c, contents)) + scriptCtx.WithFunction("Run", run(c, context)) + + // Check if the method exists + if !scriptCtx.Global().Has(method) { + return nil, fmt.Errorf(HookErrorMethodNotFound) + } + + // Call the method directly in the current thread + args = append([]interface{}{context.Map()}, args...) + if scriptCtx != nil { + return scriptCtx.CallWith(ctx, method, args...) + } + return nil, nil +} + +// Execute the assistant +func run(c *gin.Context, context chatctx.Context) func(info *v8go.FunctionCallbackInfo) *v8go.Value { + return func(info *v8go.FunctionCallbackInfo) *v8go.Value { + + // Get the args + args := info.Args() + if len(args) < 2 { + return bridge.JsException(info.Context(), "Run requires at least two arguments") + } + + // Get the assistant id + assistantID := args[0].String() + + // Get the assistant + assistant, err := Get(assistantID) + if err != nil { + return bridge.JsException(info.Context(), err.Error()) + } + + // input []chatMessage.Message + input := args[1].String() + + options := map[string]interface{}{} + if len(args) > 2 { + optionsRaw, err := bridge.GoValue(args[2], info.Context()) + if err != nil { + return bridge.JsException(info.Context(), err.Error()) + } + + // Parse the options + if optionsRaw != nil { + switch v := optionsRaw.(type) { + case string: + err := jsoniter.UnmarshalFromString(v, &options) + if err != nil { + return bridge.JsException(info.Context(), err.Error()) + } + case map[string]interface{}: + options = v + default: + return bridge.JsException(info.Context(), "Invalid options") + } + } + } + + // Execute the assistant + context.AssistantID = assistantID + context.ChatID = fmt.Sprintf("chat_%s", uuid.New().String()) // New chat id + context.Silent = true // Silent mode + err = assistant.Execute(c, context, input, options) // Execute the assistant + if err != nil { + return bridge.JsException(info.Context(), err.Error()) + } + return nil + } +} + +func sendMessage(c *gin.Context, contents *chatMessage.Contents) func(info *v8go.FunctionCallbackInfo) *v8go.Value { + return func(info *v8go.FunctionCallbackInfo) *v8go.Value { // Get the message args := info.Args() @@ -354,17 +428,5 @@ func (ast *Assistant) call(ctx context.Context, method string, c *gin.Context, c default: return bridge.JsException(info.Context(), "SendMessage requires a string or a map") } - }) - - // Check if the method exists - if !scriptCtx.Global().Has(method) { - return nil, fmt.Errorf(HookErrorMethodNotFound) } - - // Call the method directly in the current thread - args = append([]interface{}{context.Map()}, args...) - if scriptCtx != nil { - return scriptCtx.CallWith(ctx, method, args...) - } - return nil, nil } diff --git a/neo/context/context.go b/neo/context/context.go index 686d3949..4962c3a0 100644 --- a/neo/context/context.go +++ b/neo/context/context.go @@ -21,6 +21,8 @@ type Context struct { Namespace string `json:"namespace,omitempty"` Config map[string]interface{} `json:"config,omitempty"` Signal interface{} `json:"signal,omitempty"` + Silent bool `json:"silent,omitempty"` // Silent mode + Retry bool `json:"retry,omitempty"` // Retry mode Upload *FileUpload `json:"upload,omitempty"` Version bool `json:"version,omitempty"` // Version support RAG bool `json:"rag,omitempty"` // RAG support diff --git a/neo/message/message.go b/neo/message/message.go index 19e8fbe0..125d6367 100644 --- a/neo/message/message.go +++ b/neo/message/message.go @@ -35,6 +35,8 @@ type Message struct { Data map[string]interface{} `json:"-"` // data for the message Pending bool `json:"-"` // pending for the message Hidden bool `json:"hidden,omitempty"` // hidden for the message (not show in the UI and history) + Retry bool `json:"retry,omitempty"` // retry for the message + Silent bool `json:"silent,omitempty"` // silent for the message (not show in the UI and history) } // Mention represents a mention @@ -187,10 +189,9 @@ func NewAny(content interface{}) (*Message, error) { // NewOpenAI create a new message from OpenAI response func NewOpenAI(data []byte, isThinking bool) *Message { - // For Debug // For debug environment, print the response data if os.Getenv("YAO_AGENT_PRINT_RESPONSE_DATA") == "true" { - fmt.Printf("%s\n", string(data)) + log.Trace("[Response Data] %s", string(data)) } if data == nil || len(data) == 0 {