yao/neo/assistant/openai/chat.go
Max 238347c834 Refactor message handling and enhance file reading capabilities in Neo API
- Updated the message handling logic to improve error reporting and content management, ensuring more robust communication with clients.
- Introduced a new ReadBase64 method in both Local and OpenAI assistant implementations to read files and return their base64 encoded content, enhancing file handling capabilities.
- Removed the deprecated JSON message handling code, streamlining the message processing structure.
- Refactored message struct to include new fields and methods for better data management and response handling.
- Improved the chat functionality to handle attachments and user messages more effectively, ensuring a smoother user experience.
2024-12-31 16:01:48 +08:00

118 lines
2.9 KiB
Go

package openai
import (
"context"
"fmt"
"strings"
chatMessage "github.com/yaoapp/yao/neo/message"
)
// Chat the chat struct
type Chat struct {
ID string `json:"chat_id"`
ThreadID string `json:"thread_id"`
}
// NewChat create a new chat
func (ast *OpenAI) NewChat() {}
// Chat the chat
func (ast *OpenAI) Chat(ctx context.Context, messages []map[string]interface{}, option map[string]interface{}, cb func(data []byte) int) error {
if ast.openai == nil {
return fmt.Errorf("openai is not initialized")
}
requestMessages, err := ast.requestMessages(ctx, messages)
if err != nil {
return fmt.Errorf("request messages error: %s", err.Error())
}
_, ext := ast.openai.ChatCompletionsWith(ctx, requestMessages, option, cb)
if ext != nil {
return fmt.Errorf("openai chat completions with error: %s", ext.Message)
}
return nil
}
func (ast *OpenAI) requestMessages(ctx context.Context, messages []map[string]interface{}) ([]map[string]interface{}, error) {
newMessages := []map[string]interface{}{}
length := len(messages)
for index, message := range messages {
role, ok := message["role"].(string)
if !ok {
return nil, fmt.Errorf("role must be string")
}
content, ok := message["content"].(string)
if !ok {
return nil, fmt.Errorf("content must be string")
}
newMessage := map[string]interface{}{
"role": role,
"content": content,
}
// Handle name if present
if name, ok := message["name"].(string); ok {
newMessage["name"] = name
}
newMessage["content"] = content
// Special handling for user messages with JSON content last message
if role == "user" && index == length-1 {
content = strings.TrimSpace(content)
msg, err := chatMessage.NewString(content)
if err != nil {
return nil, fmt.Errorf("new string error: %s", err.Error())
}
newMessage["content"] = msg.Text
if msg.Attachments != nil {
content, err := ast.withAttachments(ctx, msg)
if err != nil {
return nil, fmt.Errorf("with attachments error: %s", err.Error())
}
newMessage["content"] = content
}
}
newMessages = append(newMessages, newMessage)
}
return newMessages, nil
}
func (ast *OpenAI) withAttachments(ctx context.Context, msg *chatMessage.Message) ([]map[string]interface{}, error) {
contents := []map[string]interface{}{{"type": "text", "text": msg.Text}}
images := []string{}
for _, attachment := range msg.Attachments {
if strings.HasPrefix(attachment.ContentType, "image/") {
images = append(images, attachment.FileID)
}
}
if len(images) == 0 {
return contents, nil
}
for _, image := range images {
bytes64, err := ast.ReadBase64(ctx, image)
if err != nil {
return nil, fmt.Errorf("read base64 error: %s", err.Error())
}
contents = append(contents, map[string]interface{}{
"type": "image_url",
"image_url": map[string]string{
"url": fmt.Sprintf("data:image/jpeg;base64,%s", bytes64),
},
},
)
}
return contents, nil
}