diff --git a/neo/assistant/api.go b/neo/assistant/api.go index fc731c4f..a900c49e 100644 --- a/neo/assistant/api.go +++ b/neo/assistant/api.go @@ -620,27 +620,38 @@ func (ast *Assistant) withPrompts(messages []chatMessage.Message) []chatMessage. raw, _ := jsoniter.MarshalToString(ast.Tools.Tools) messages = append(messages, *chatMessage.New().Map(map[string]interface{}{ "role": "system", + "name": "TOOL_CALLS_SCHEMA", "content": raw, })) - // Add the default system prompts for tool calls messages = append(messages, *chatMessage.New().Map(map[string]interface{}{ "role": "system", - "content": "## Tool Calls Match Rules:\n" + - "1. if the user's question is about the tool_calls, just answer one of the tool_calls, do not provide any additional information.\n" + - "2. if the user's question is not about the tool_calls, just answer the user's question directly.\n" + - "3. You can only use the functions defined in tool_calls. If none exist, reply directly to the user.", + "name": "TOOL_CALLS", + "content": "## Tool Response Format\n" + + "1. If no matching function exists in TOOL_CALLS_SCHEMA, respond normally without using tool calls\n" + + "2. When using tools, wrap function calls in and tags\n" + + "3. The tool call must be a valid JSON object\n" + + "4. Follow the JSON Schema defined in TOOL_CALLS_SCHEMA\n" + + "5. One complete tool call per response\n" + + "6. Parameter values MUST strictly follow the descriptions and validation rules defined in properties\n" + + "7. For each parameter, carefully check and comply with:\n" + + " - Data type requirements\n" + + " - Format restrictions\n" + + " - Value range limitations\n" + + " - Pattern matching rules\n" + + " - Required field validations\n\n" + + "Example:\n" + + "\n" + `{"function":"","arguments":{"":""}}` + "\n", })) messages = append(messages, *chatMessage.New().Map(map[string]interface{}{ "role": "system", - "content": "## Tool Calls Response Rules:\n" + - "1. The response should be a valid JSON object:\n" + - " 1.1. e.g: {\"function\":\"function_name\",\"arguments\":{\"arg1\":\"xxxx\"}}\n" + - " 1.2. strict the example format, do not add any additional information.\n" + - " 1.3. The JSON object should be wrapped by and .\n" + - "2. The structure of the JSON object is { \"arguments\": {...}, function:\"function_name\"}\n" + - "3. The function_name should be the name of the function defined in tool_calls.\n" + - "4. The arguments should be the arguments of the function defined in tool_calls.\n", + "name": "TOOL_CALLS", + "content": "## Tool Usage Guidelines\n" + + "1. Use functions defined in TOOL_CALLS_SCHEMA only when they match your needs\n" + + "2. If no matching function exists, respond normally as a helpful assistant\n" + + "3. When using tools, arguments must match the schema definition exactly\n" + + "4. All parameter values must strictly adhere to the validation rules specified in properties\n" + + "5. Never skip or ignore any validation requirements defined in the schema", })) // Add tool_calls prompts @@ -747,8 +758,13 @@ func (ast *Assistant) requestMessages(ctx context.Context, messages []chatMessag "content": content, } + // Keep the name for user messages if name := message.Name; name != "" { - newMessage["name"] = stringHash(name) + if role != "system" { + newMessage["name"] = stringHash(name) + } else { + newMessage["name"] = name + } } // Special handling for user messages with JSON content last message @@ -786,9 +802,9 @@ 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("--------------------------------") + fmt.Println("--- REQUEST_MESSAGES -----------------------------") utils.Dump(newMessages) - fmt.Println("--------------------------------") + fmt.Println("--- END REQUEST_MESSAGES -----------------------------") } return newMessages, nil diff --git a/neo/assistant/hooks.go b/neo/assistant/hooks.go index e6dc7ff5..ad61278b 100644 --- a/neo/assistant/hooks.go +++ b/neo/assistant/hooks.go @@ -3,6 +3,7 @@ package assistant import ( "context" "fmt" + "os" "strings" "time" @@ -154,26 +155,28 @@ func (ast *Assistant) HookDone(c *gin.Context, context chatctx.Context, input [] props := map[string]interface{}{} if text, ok := data.Props["text"].(string); ok { - // Format the text keep only the and inner text - parts := strings.Split(text, "") - if len(parts) > 1 { - text = parts[1] + // Extract the content between and tags more reliably + startTag := "" + endTag := "" + startIndex := strings.Index(text, startTag) + if startIndex != -1 { + // Find the content after + content := text[startIndex+len(startTag):] + endIndex := strings.LastIndex(content, endTag) + if endIndex != -1 { + // Extract the content between tags + 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 ----") + } + } } - // Format the text keep only the and inner text - parts = strings.Split(text, "") - if len(parts) > 1 { - text = parts[0] - } - - // Escape %7B and %7b to {, %7D and %7d to } - text = strings.ReplaceAll(text, "%7B", "{") - text = strings.ReplaceAll(text, "%7b", "{") - text = strings.ReplaceAll(text, "%7D", "}") - text = strings.ReplaceAll(text, "%7d", "}") - // Parse the text into props - err := jsoniter.UnmarshalFromString(text, &props) + err := ParseJSON(text, &props) if err != nil { props["error"] = fmt.Sprintf("Can not parse the tool call: %s\n--original--\n%s", err.Error(), text) } diff --git a/neo/assistant/utils.go b/neo/assistant/utils.go index c3114269..41ae2a26 100644 --- a/neo/assistant/utils.go +++ b/neo/assistant/utils.go @@ -5,7 +5,10 @@ import ( "encoding/hex" "fmt" "strconv" + "strings" "time" + + jsoniter "github.com/json-iterator/go" ) func getTimestamp(v interface{}) (int64, error) { @@ -51,3 +54,33 @@ func stringHash(v string) string { h.Write([]byte(v)) return hex.EncodeToString(h.Sum(nil)) } + +// ParseJSON attempts to parse a potentially malformed JSON string +// It tries different approaches: +// 1. Parse as-is +// 2. Add a missing closing brace +// 3. Remove an extra closing brace +func ParseJSON(jsonStr string, v interface{}) error { + // Try parsing as-is first + err := jsoniter.UnmarshalFromString(jsonStr, v) + if err == nil { + return nil + } + originalErr := err + + // Try adding a closing brace + if err := jsoniter.UnmarshalFromString(jsonStr+"}", v); err == nil { + return nil + } + + // Try removing last closing brace if it exists + if strings.HasSuffix(jsonStr, "}") { + trimmed := strings.TrimSuffix(jsonStr, "}") + if err := jsoniter.UnmarshalFromString(trimmed, v); err == nil { + return nil + } + } + + // If all attempts fail, return the original error + return originalErr +}