Merge pull request #856 from trheyi/main
Enhance tool call handling and message processing
This commit is contained in:
commit
c575bd9d9d
4 changed files with 71 additions and 94 deletions
|
|
@ -4,11 +4,13 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"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"
|
||||||
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"
|
||||||
)
|
)
|
||||||
|
|
@ -295,6 +297,9 @@ func (ast *Assistant) streamChat(
|
||||||
isFirst := true
|
isFirst := true
|
||||||
isFirstThink := true
|
isFirstThink := true
|
||||||
isThinking := false
|
isThinking := false
|
||||||
|
|
||||||
|
isFirstTool := true
|
||||||
|
isTool := false
|
||||||
currentMessageID := ""
|
currentMessageID := ""
|
||||||
err := ast.Chat(c.Request.Context(), messages, options, func(data []byte) int {
|
err := ast.Chat(c.Request.Context(), messages, options, func(data []byte) int {
|
||||||
select {
|
select {
|
||||||
|
|
@ -350,6 +355,32 @@ func (ast *Assistant) streamChat(
|
||||||
contents.ClearToken()
|
contents.ClearToken()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for native tool_calls response
|
||||||
|
if msg.Type == "tool_calls_native" {
|
||||||
|
if isFirstTool {
|
||||||
|
msg.Text = "<tool>\n" + msg.Text // add the tool_calls begin tag
|
||||||
|
isFirstTool = false
|
||||||
|
isTool = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for tool response
|
||||||
|
if isTool && msg.Type != "tool_calls_native" {
|
||||||
|
|
||||||
|
if msg.IsDone {
|
||||||
|
end := chatMessage.New().Map(map[string]interface{}{"text": "}\n</tool>\n", "type": "tool", "delta": true})
|
||||||
|
end.Write(c.Writer)
|
||||||
|
end.ID = currentMessageID
|
||||||
|
end.AppendTo(contents)
|
||||||
|
contents.UpdateType("tool", map[string]interface{}{"text": contents.Text()}, currentMessageID)
|
||||||
|
isTool = false
|
||||||
|
} else {
|
||||||
|
msg.Text = "\n</tool>\n" + msg.Text // add the tool_calls close tag
|
||||||
|
}
|
||||||
|
|
||||||
|
isTool = false
|
||||||
|
}
|
||||||
|
|
||||||
delta := msg.String()
|
delta := msg.String()
|
||||||
|
|
||||||
// Chunk the delta
|
// Chunk the delta
|
||||||
|
|
@ -404,9 +435,14 @@ func (ast *Assistant) streamChat(
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
|
|
||||||
// Write the message to the stream
|
// Write the message to the stream
|
||||||
|
msgType := msg.Type
|
||||||
|
if msgType == "tool_calls_native" {
|
||||||
|
msgType = "tool"
|
||||||
|
}
|
||||||
|
|
||||||
output := chatMessage.New().Map(map[string]interface{}{
|
output := chatMessage.New().Map(map[string]interface{}{
|
||||||
"text": delta,
|
"text": delta,
|
||||||
"type": msg.Type,
|
"type": msgType,
|
||||||
"done": msg.IsDone,
|
"done": msg.IsDone,
|
||||||
"delta": true,
|
"delta": true,
|
||||||
})
|
})
|
||||||
|
|
@ -691,7 +727,7 @@ func (ast *Assistant) requestMessages(ctx context.Context, messages []chatMessag
|
||||||
}
|
}
|
||||||
|
|
||||||
if name := message.Name; name != "" {
|
if name := message.Name; name != "" {
|
||||||
newMessage["name"] = name
|
newMessage["name"] = stringHash(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special handling for user messages with JSON content last message
|
// Special handling for user messages with JSON content last message
|
||||||
|
|
@ -726,6 +762,14 @@ func (ast *Assistant) requestMessages(ctx context.Context, messages []chatMessag
|
||||||
|
|
||||||
newMessages = append(newMessages, newMessage)
|
newMessages = append(newMessages, newMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For debug environment, print the request messages
|
||||||
|
if os.Getenv("YAO_AGENT_PRINT_REQUEST_MESSAGES") == "true" {
|
||||||
|
fmt.Println("--------------------------------")
|
||||||
|
utils.Dump(newMessages)
|
||||||
|
fmt.Println("--------------------------------")
|
||||||
|
}
|
||||||
|
|
||||||
return newMessages, nil
|
return newMessages, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package assistant
|
package assistant
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -42,3 +44,10 @@ func timeToMySQLFormat(ts int64) string {
|
||||||
}
|
}
|
||||||
return time.Unix(ts/1e9, ts%1e9).Format("2006-01-02 15:04:05")
|
return time.Unix(ts/1e9, ts%1e9).Format("2006-01-02 15:04:05")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stringHash returns the sha256 hash of the string
|
||||||
|
func stringHash(v string) string {
|
||||||
|
h := sha256.New()
|
||||||
|
h.Write([]byte(v))
|
||||||
|
return hex.EncodeToString(h.Sum(nil))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,12 +31,10 @@ type Contents struct {
|
||||||
|
|
||||||
// Data the data of the content
|
// Data the data of the content
|
||||||
type Data struct {
|
type Data struct {
|
||||||
Type string `json:"type"` // text, function, error, think, tool
|
Type string `json:"type"` // text, function, error, think, tool
|
||||||
ID string `json:"id"` // the id of the content
|
ID string `json:"id"` // the id of the content
|
||||||
Function string `json:"function"` // the function name
|
Bytes []byte `json:"bytes"` // the content bytes
|
||||||
Bytes []byte `json:"bytes"` // the content bytes
|
Props map[string]interface{} `json:"props"` // the props
|
||||||
Arguments []byte `json:"arguments,omitempty"` // the function arguments
|
|
||||||
Props map[string]interface{} `json:"props"` // the props
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewContents create a new contents
|
// NewContents create a new contents
|
||||||
|
|
@ -117,22 +115,6 @@ func (c *Contents) NewText(bytes []byte, id ...string) *Contents {
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTool create a new tool data and append to the contents
|
|
||||||
func (c *Contents) NewTool(function string, arguments []byte, id ...string) *Contents {
|
|
||||||
|
|
||||||
data := Data{
|
|
||||||
Type: "tool",
|
|
||||||
Function: function,
|
|
||||||
Arguments: arguments,
|
|
||||||
}
|
|
||||||
if len(id) > 0 && id[0] != "" {
|
|
||||||
data.ID = id[0]
|
|
||||||
}
|
|
||||||
c.Data = append(c.Data, data)
|
|
||||||
c.Current++
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewType create a new type data and append to the contents
|
// NewType create a new type data and append to the contents
|
||||||
func (c *Contents) NewType(typ string, props map[string]interface{}, id ...string) *Contents {
|
func (c *Contents) NewType(typ string, props map[string]interface{}, id ...string) *Contents {
|
||||||
|
|
||||||
|
|
@ -163,15 +145,6 @@ func (c *Contents) UpdateType(typ string, props map[string]interface{}, id ...st
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetToolID set the id of the current tool content
|
|
||||||
func (c *Contents) SetToolID(id string) *Contents {
|
|
||||||
if c.Current == -1 {
|
|
||||||
c.NewTool("", []byte{})
|
|
||||||
}
|
|
||||||
c.Data[c.Current].ID = id
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewError create a new error data and append to the contents
|
// NewError create a new error data and append to the contents
|
||||||
func (c *Contents) NewError(err []byte) *Contents {
|
func (c *Contents) NewError(err []byte) *Contents {
|
||||||
c.Data = append(c.Data, Data{
|
c.Data = append(c.Data, Data{
|
||||||
|
|
@ -196,16 +169,6 @@ func (c *Contents) AppendText(bytes []byte, id ...string) *Contents {
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppendTool append the tool to the current content
|
|
||||||
func (c *Contents) AppendTool(arguments []byte, id ...string) *Contents {
|
|
||||||
if c.Current == -1 {
|
|
||||||
c.NewTool("", arguments, id...)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
c.Data[c.Current].Arguments = append(c.Data[c.Current].Arguments, arguments...)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppendError append the error to the current content
|
// AppendError append the error to the current content
|
||||||
func (c *Contents) AppendError(err []byte) *Contents {
|
func (c *Contents) AppendError(err []byte) *Contents {
|
||||||
if c.Current == -1 {
|
if c.Current == -1 {
|
||||||
|
|
@ -254,19 +217,6 @@ func (data *Data) Map() (map[string]interface{}, error) {
|
||||||
v["props"] = data.Props
|
v["props"] = data.Props
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.Arguments != nil && len(data.Arguments) > 0 {
|
|
||||||
var vv interface{} = nil
|
|
||||||
err := jsoniter.Unmarshal(data.Arguments, &vv)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
v["arguments"] = vv
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.Function != "" {
|
|
||||||
v["function"] = data.Function
|
|
||||||
}
|
|
||||||
|
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -287,18 +237,5 @@ func (data *Data) MarshalJSON() ([]byte, error) {
|
||||||
v["props"] = data.Props
|
v["props"] = data.Props
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.Arguments != nil && len(data.Arguments) > 0 {
|
|
||||||
var vv interface{} = nil
|
|
||||||
err := jsoniter.Unmarshal(data.Arguments, &vv)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
v["arguments"] = vv
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.Function != "" {
|
|
||||||
v["function"] = data.Function
|
|
||||||
}
|
|
||||||
|
|
||||||
return jsoniter.Marshal(v)
|
return jsoniter.Marshal(v)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -191,7 +191,8 @@ func NewAny(content interface{}) (*Message, error) {
|
||||||
func NewOpenAI(data []byte, isThinking bool) *Message {
|
func NewOpenAI(data []byte, isThinking bool) *Message {
|
||||||
|
|
||||||
// For Debug
|
// For Debug
|
||||||
if os.Getenv("YAO_AGENT_DEBUG") == "true" {
|
// For debug environment, print the response data
|
||||||
|
if os.Getenv("YAO_AGENT_PRINT_RESPONSE_DATA") == "true" {
|
||||||
fmt.Printf("%s\n", string(data))
|
fmt.Printf("%s\n", string(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -217,9 +218,14 @@ func NewOpenAI(data []byte, isThinking bool) *Message {
|
||||||
|
|
||||||
msg.Type = "tool_calls_native"
|
msg.Type = "tool_calls_native"
|
||||||
if len(toolCalls.Choices) > 0 && len(toolCalls.Choices[0].Delta.ToolCalls) > 0 {
|
if len(toolCalls.Choices) > 0 && len(toolCalls.Choices[0].Delta.ToolCalls) > 0 {
|
||||||
msg.Props["id"] = toolCalls.Choices[0].Delta.ToolCalls[0].ID
|
id := toolCalls.Choices[0].Delta.ToolCalls[0].ID
|
||||||
msg.Props["function"] = toolCalls.Choices[0].Delta.ToolCalls[0].Function.Name
|
function := toolCalls.Choices[0].Delta.ToolCalls[0].Function.Name
|
||||||
msg.Text = toolCalls.Choices[0].Delta.ToolCalls[0].Function.Arguments
|
arguments := toolCalls.Choices[0].Delta.ToolCalls[0].Function.Arguments
|
||||||
|
text := arguments
|
||||||
|
if id != "" {
|
||||||
|
text = fmt.Sprintf(`{"id": "%s", "function": "%s", "arguments": %s`, id, function, arguments)
|
||||||
|
}
|
||||||
|
msg.Text = text
|
||||||
}
|
}
|
||||||
|
|
||||||
case strings.Contains(text, `"delta":{`) && strings.Contains(text, `"content":`):
|
case strings.Contains(text, `"delta":{`) && strings.Contains(text, `"content":`):
|
||||||
|
|
@ -322,7 +328,7 @@ func (m *Message) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch typ {
|
switch typ {
|
||||||
case "text", "think", "tool":
|
case "text", "think", "tool", "tool_calls_native":
|
||||||
return m.Text
|
return m.Text
|
||||||
|
|
||||||
case "error":
|
case "error":
|
||||||
|
|
@ -392,7 +398,7 @@ func (m *Message) AppendTo(contents *Contents) *Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch m.Type {
|
switch m.Type {
|
||||||
case "text", "think", "tool":
|
case "text", "think", "tool", "tool_calls_native":
|
||||||
if m.Text != "" {
|
if m.Text != "" {
|
||||||
if m.IsNew {
|
if m.IsNew {
|
||||||
contents.NewText([]byte(m.Text), m.ID)
|
contents.NewText([]byte(m.Text), m.ID)
|
||||||
|
|
@ -403,25 +409,6 @@ func (m *Message) AppendTo(contents *Contents) *Message {
|
||||||
}
|
}
|
||||||
return m
|
return m
|
||||||
|
|
||||||
case "tool_calls_native":
|
|
||||||
|
|
||||||
// Set function name
|
|
||||||
new := false
|
|
||||||
if name, ok := m.Props["tool"].(string); ok && name != "" {
|
|
||||||
contents.NewTool(name, []byte(m.Text))
|
|
||||||
new = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set id
|
|
||||||
if id, ok := m.Props["id"].(string); ok && id != "" {
|
|
||||||
contents.SetToolID(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !new {
|
|
||||||
contents.AppendTool([]byte(m.Text))
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
|
|
||||||
case "loading", "error", "action": // Ignore loading, action and error messages
|
case "loading", "error", "action": // Ignore loading, action and error messages
|
||||||
return m
|
return m
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue