Add retry and silent modes to assistant chat processing
- Introduce retry and silent mode flags in context and message structures - Modify API and hooks to support new context modes - Update message handling to propagate retry and silent flags - Replace debug print statements with structured logging - Enhance tool call and message processing with new context options
This commit is contained in:
parent
14bcaa0bf4
commit
13c4e65a95
4 changed files with 113 additions and 23 deletions
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/yaoapp/gou/fs"
|
"github.com/yaoapp/gou/fs"
|
||||||
"github.com/yaoapp/kun/utils"
|
"github.com/yaoapp/kun/log"
|
||||||
chatctx "github.com/yaoapp/yao/neo/context"
|
chatctx "github.com/yaoapp/yao/neo/context"
|
||||||
chatMessage "github.com/yaoapp/yao/neo/message"
|
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")
|
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) {
|
switch v := next.Payload["input"].(type) {
|
||||||
case string:
|
case string:
|
||||||
messages := chatMessage.Message{}
|
messages := chatMessage.Message{}
|
||||||
|
|
@ -338,6 +346,10 @@ func (ast *Assistant) streamChat(
|
||||||
return 1 // continue
|
return 1 // continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retry mode
|
||||||
|
msg.Retry = ctx.Retry // Retry mode
|
||||||
|
msg.Silent = ctx.Silent // Silent mode
|
||||||
|
|
||||||
// Handle error
|
// Handle error
|
||||||
if msg.Type == "error" {
|
if msg.Type == "error" {
|
||||||
value := msg.String()
|
value := msg.String()
|
||||||
|
|
@ -348,7 +360,10 @@ func (ast *Assistant) streamChat(
|
||||||
value = res.Error
|
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
|
return 0 // break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -468,6 +483,9 @@ func (ast *Assistant) streamChat(
|
||||||
"delta": true,
|
"delta": true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
output.Retry = ctx.Retry // Retry mode
|
||||||
|
output.Silent = ctx.Silent // Silent mode
|
||||||
|
|
||||||
if isFirst {
|
if isFirst {
|
||||||
output.Assistant(ast.ID, ast.Name, ast.Avatar)
|
output.Assistant(ast.ID, ast.Name, ast.Avatar)
|
||||||
isFirst = false
|
isFirst = false
|
||||||
|
|
@ -489,6 +507,8 @@ func (ast *Assistant) streamChat(
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"delta": true,
|
"delta": true,
|
||||||
"done": true,
|
"done": true,
|
||||||
|
"retry": ctx.Retry,
|
||||||
|
"silent": ctx.Silent,
|
||||||
}).
|
}).
|
||||||
Write(c.Writer)
|
Write(c.Writer)
|
||||||
}
|
}
|
||||||
|
|
@ -521,6 +541,8 @@ func (ast *Assistant) streamChat(
|
||||||
output := chatMessage.New().Done()
|
output := chatMessage.New().Done()
|
||||||
if res != nil && res.Output != nil {
|
if res != nil && res.Output != nil {
|
||||||
output = chatMessage.New().Map(map[string]interface{}{"text": res.Output, "done": true})
|
output = chatMessage.New().Map(map[string]interface{}{"text": res.Output, "done": true})
|
||||||
|
output.Retry = ctx.Retry
|
||||||
|
output.Silent = ctx.Silent
|
||||||
}
|
}
|
||||||
output.Write(c.Writer)
|
output.Write(c.Writer)
|
||||||
done <- true
|
done <- true
|
||||||
|
|
@ -542,6 +564,8 @@ func (ast *Assistant) streamChat(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error: %s", err.Error())
|
return fmt.Errorf("error: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
msg.Retry = ctx.Retry
|
||||||
|
msg.Silent = ctx.Silent
|
||||||
msg.Done().Write(c.Writer)
|
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
|
// For debug environment, print the request messages
|
||||||
if os.Getenv("YAO_AGENT_PRINT_REQUEST_MESSAGES") == "true" {
|
if os.Getenv("YAO_AGENT_PRINT_REQUEST_MESSAGES") == "true" {
|
||||||
fmt.Println("--- REQUEST_MESSAGES -----------------------------")
|
for _, message := range newMessages {
|
||||||
utils.Dump(newMessages)
|
raw, _ := jsoniter.MarshalToString(message)
|
||||||
fmt.Println("--- END REQUEST_MESSAGES -----------------------------")
|
log.Trace("[Request Message] %s", raw)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return newMessages, nil
|
return newMessages, nil
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,10 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/yaoapp/gou/runtime/v8/bridge"
|
"github.com/yaoapp/gou/runtime/v8/bridge"
|
||||||
|
"github.com/yaoapp/kun/log"
|
||||||
chatctx "github.com/yaoapp/yao/neo/context"
|
chatctx "github.com/yaoapp/yao/neo/context"
|
||||||
"github.com/yaoapp/yao/neo/message"
|
"github.com/yaoapp/yao/neo/message"
|
||||||
chatMessage "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 = content[:endIndex]
|
||||||
text = strings.TrimSpace(text)
|
text = strings.TrimSpace(text)
|
||||||
if os.Getenv("YAO_AGENT_PRINT_TOOL_CALL") == "true" {
|
if os.Getenv("YAO_AGENT_PRINT_TOOL_CALL") == "true" {
|
||||||
fmt.Println("---- EXTRACTED TOOL CALL ----")
|
log.Trace("[TOOL CALL] %s", text)
|
||||||
fmt.Println(text)
|
|
||||||
fmt.Println("---- END EXTRACTED TOOL CALL ----")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -309,7 +309,81 @@ func (ast *Assistant) call(ctx context.Context, method string, c *gin.Context, c
|
||||||
defer scriptCtx.Close()
|
defer scriptCtx.Close()
|
||||||
|
|
||||||
// Add sendMessage function to the script context
|
// 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
|
// Get the message
|
||||||
args := info.Args()
|
args := info.Args()
|
||||||
|
|
@ -354,17 +428,5 @@ func (ast *Assistant) call(ctx context.Context, method string, c *gin.Context, c
|
||||||
default:
|
default:
|
||||||
return bridge.JsException(info.Context(), "SendMessage requires a string or a map")
|
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
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ type Context struct {
|
||||||
Namespace string `json:"namespace,omitempty"`
|
Namespace string `json:"namespace,omitempty"`
|
||||||
Config map[string]interface{} `json:"config,omitempty"`
|
Config map[string]interface{} `json:"config,omitempty"`
|
||||||
Signal interface{} `json:"signal,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"`
|
Upload *FileUpload `json:"upload,omitempty"`
|
||||||
Version bool `json:"version,omitempty"` // Version support
|
Version bool `json:"version,omitempty"` // Version support
|
||||||
RAG bool `json:"rag,omitempty"` // RAG support
|
RAG bool `json:"rag,omitempty"` // RAG support
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,8 @@ type Message struct {
|
||||||
Data map[string]interface{} `json:"-"` // data for the message
|
Data map[string]interface{} `json:"-"` // data for the message
|
||||||
Pending bool `json:"-"` // pending 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)
|
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
|
// Mention represents a mention
|
||||||
|
|
@ -187,10 +189,9 @@ func NewAny(content interface{}) (*Message, error) {
|
||||||
// NewOpenAI create a new message from OpenAI response
|
// NewOpenAI create a new message from OpenAI response
|
||||||
func NewOpenAI(data []byte, isThinking bool) *Message {
|
func NewOpenAI(data []byte, isThinking bool) *Message {
|
||||||
|
|
||||||
// For Debug
|
|
||||||
// For debug environment, print the response data
|
// For debug environment, print the response data
|
||||||
if os.Getenv("YAO_AGENT_PRINT_RESPONSE_DATA") == "true" {
|
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 {
|
if data == nil || len(data) == 0 {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue