feat: Enhance context management and update asset metadata in SUI
- Updated modification times for various asset files in bindata.go to reflect recent changes. - Improved context handling in the API by adding support for silent mode, history visibility, and client type. - Refactored assistant ID usage to ensure consistent access to the default assistant across various methods. - Enhanced the Agent class in libsui/agent.ts to manage silent mode and history visibility settings effectively.
This commit is contained in:
parent
afc2971830
commit
712bb99ab1
10 changed files with 335 additions and 501 deletions
168
data/bindata.go
168
data/bindata.go
File diff suppressed because one or more lines are too long
235
neo/api.go
235
neo/api.go
|
|
@ -134,15 +134,6 @@ func (neo *DSL) API(router *gin.Engine, path string) error {
|
|||
// curl -X GET 'http://localhost:5099/api/__yao/neo/mentions?keywords=assistant&token=xxx'
|
||||
router.GET(path+"/mentions", append(middlewares, neo.handleMentions)...)
|
||||
|
||||
// Generation endpoints
|
||||
// Generate custom content example:
|
||||
// curl -X GET 'http://localhost:5099/api/__yao/neo/generate?content=Generate+something&type=custom&system_prompt=You+are+a+helpful+assistant&chat_id=chat_123&token=xxx'
|
||||
// curl -X POST 'http://localhost:5099/api/__yao/neo/generate' \
|
||||
// -H 'Content-Type: application/json' \
|
||||
// -d '{"content": "Generate something", "type": "custom", "system_prompt": "You are a helpful assistant", "chat_id": "chat_123", "token": "xxx"}'
|
||||
router.GET(path+"/generate", append(middlewares, neo.handleGenerateCustom)...)
|
||||
router.POST(path+"/generate", append(middlewares, neo.handleGenerateCustom)...)
|
||||
|
||||
// Generate title example:
|
||||
// curl -X GET 'http://localhost:5099/api/__yao/neo/generate/title?content=Chat+content&chat_id=chat_123&token=xxx'
|
||||
// curl -X POST 'http://localhost:5099/api/__yao/neo/generate/title' \
|
||||
|
|
@ -238,6 +229,24 @@ func (neo *DSL) handleChat(c *gin.Context) {
|
|||
ctx = chatctx.WithAssistantID(ctx, assistantID)
|
||||
}
|
||||
|
||||
// Set the silent mode
|
||||
silent := c.Query("silent")
|
||||
if silent == "true" || silent == "1" {
|
||||
ctx = chatctx.WithSilent(ctx, true)
|
||||
}
|
||||
|
||||
// Set the history visible
|
||||
historyVisible := c.Query("history_visible")
|
||||
if historyVisible != "" {
|
||||
ctx = chatctx.WithHistoryVisible(ctx, historyVisible == "true" || historyVisible == "1")
|
||||
}
|
||||
|
||||
// Set the client type
|
||||
clientType := c.Query("client_type")
|
||||
if clientType != "" {
|
||||
ctx = chatctx.WithClientType(ctx, clientType)
|
||||
}
|
||||
|
||||
err := neo.Answer(ctx, content, c)
|
||||
|
||||
// Error handling
|
||||
|
|
@ -485,7 +494,7 @@ func (neo *DSL) handleChatLatest(c *gin.Context) {
|
|||
// Create a new chat
|
||||
if len(chats.Groups) == 0 || len(chats.Groups[0].Chats) == 0 {
|
||||
|
||||
assistantID := neo.Use
|
||||
assistantID := neo.Use.Default
|
||||
queryAssistantID := c.Query("assistant_id")
|
||||
if queryAssistantID != "" {
|
||||
assistantID = queryAssistantID
|
||||
|
|
@ -504,7 +513,7 @@ func (neo *DSL) handleChatLatest(c *gin.Context) {
|
|||
"assistant_id": ast.ID,
|
||||
"assistant_name": ast.Name,
|
||||
"assistant_avatar": ast.Avatar,
|
||||
"assistant_deleteable": neo.Use != ast.ID,
|
||||
"assistant_deleteable": neo.Use.Default != ast.ID,
|
||||
}})
|
||||
c.Done()
|
||||
return
|
||||
|
|
@ -530,7 +539,7 @@ func (neo *DSL) handleChatLatest(c *gin.Context) {
|
|||
chat.Chat["assistant_id"] = neo.Use
|
||||
|
||||
// Get the assistant info
|
||||
ast, err := assistant.Get(neo.Use)
|
||||
ast, err := assistant.Get(neo.Use.Default)
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
|
||||
c.Done()
|
||||
|
|
@ -573,7 +582,7 @@ func (neo *DSL) handleChatDetail(c *gin.Context) {
|
|||
chat.Chat["assistant_id"] = neo.Use
|
||||
|
||||
// Get the assistant info
|
||||
ast, err := assistant.Get(neo.Use)
|
||||
ast, err := assistant.Get(neo.Use.Default)
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
|
||||
c.Done()
|
||||
|
|
@ -583,7 +592,7 @@ func (neo *DSL) handleChatDetail(c *gin.Context) {
|
|||
chat.Chat["assistant_avatar"] = ast.Avatar
|
||||
}
|
||||
|
||||
chat.Chat["assistant_deleteable"] = neo.Use != chat.Chat["assistant_id"]
|
||||
chat.Chat["assistant_deleteable"] = neo.Use.Default != chat.Chat["assistant_id"]
|
||||
c.JSON(200, map[string]interface{}{"data": chat})
|
||||
c.Done()
|
||||
}
|
||||
|
|
@ -659,20 +668,6 @@ func (neo *DSL) handleChatUpdate(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
// If content is not empty, Generate the chat title
|
||||
if body.Content != "" {
|
||||
ctx, cancel := chatctx.NewWithCancel(sid, c.Query("chat_id"), "")
|
||||
defer cancel()
|
||||
|
||||
title, err := neo.GenerateChatTitle(ctx, body.Content, c, true)
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
|
||||
c.Done()
|
||||
return
|
||||
}
|
||||
body.Title = title
|
||||
}
|
||||
|
||||
if body.Title == "" {
|
||||
c.JSON(400, gin.H{"message": "title is required", "code": 400})
|
||||
c.Done()
|
||||
|
|
@ -813,155 +808,81 @@ func (r *generateResponse) send(key string) {
|
|||
|
||||
// handleGenerateTitle handles generating a chat title
|
||||
func (neo *DSL) handleGenerateTitle(c *gin.Context) {
|
||||
var content string
|
||||
if c.Request.Method == "GET" {
|
||||
content = c.Query("content")
|
||||
} else {
|
||||
var body struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
if err := c.BindJSON(&body); err != nil {
|
||||
// For SSE requests, send error message in SSE format
|
||||
if strings.Contains(c.GetHeader("Accept"), "text/event-stream") {
|
||||
c.Header("Content-Type", "text/event-stream;charset=utf-8")
|
||||
c.Header("Cache-Control", "no-cache")
|
||||
c.Header("Connection", "keep-alive")
|
||||
msg := message.New().Error("invalid request body").Done()
|
||||
msg.Write(c.Writer)
|
||||
return
|
||||
}
|
||||
c.JSON(400, gin.H{"message": "invalid request body", "code": 400})
|
||||
return
|
||||
}
|
||||
content = body.Content
|
||||
// Set headers for SSE
|
||||
c.Header("Content-Type", "text/event-stream;charset=utf-8")
|
||||
c.Header("Cache-Control", "no-cache")
|
||||
c.Header("Connection", "keep-alive")
|
||||
|
||||
sid := c.GetString("__sid")
|
||||
if sid == "" {
|
||||
sid = uuid.New().String()
|
||||
}
|
||||
|
||||
resp := &generateResponse{
|
||||
c: c,
|
||||
sid: c.GetString("__sid"),
|
||||
content: content,
|
||||
}
|
||||
|
||||
// For SSE requests, set headers before validation
|
||||
if strings.Contains(c.GetHeader("Accept"), "text/event-stream") {
|
||||
c.Header("Content-Type", "text/event-stream;charset=utf-8")
|
||||
c.Header("Cache-Control", "no-cache")
|
||||
c.Header("Connection", "keep-alive")
|
||||
}
|
||||
|
||||
if !resp.validate() {
|
||||
content := c.Query("content")
|
||||
if content == "" {
|
||||
msg := message.New().Error("content is required").Done()
|
||||
msg.Write(c.Writer)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := chatctx.NewWithCancel(resp.sid, c.Query("chat_id"), "")
|
||||
defer cancel()
|
||||
chatID := fmt.Sprintf("generate_title_%d", time.Now().UnixNano())
|
||||
|
||||
// Use silent mode for regular HTTP requests, streaming for SSE
|
||||
silent := !strings.Contains(c.GetHeader("Accept"), "text/event-stream")
|
||||
resp.result, resp.err = neo.GenerateChatTitle(ctx, resp.content, c, silent)
|
||||
resp.send("result")
|
||||
// Set the context with validated chat_id
|
||||
ctx, cancel := chatctx.NewWithCancel(sid, chatID, c.Query("context"))
|
||||
defer cancel()
|
||||
defer ctx.Release() // Release the context after the request is done
|
||||
|
||||
// Set the assistant ID
|
||||
ctx = chatctx.WithHistoryVisible(ctx, false)
|
||||
ctx = chatctx.WithAssistantID(ctx, neo.Use.Title)
|
||||
|
||||
err := neo.Answer(ctx, content, c)
|
||||
|
||||
// Error handling
|
||||
if err != nil {
|
||||
message.New().Done().Error(err).Write(c.Writer)
|
||||
c.Done()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// handleGeneratePrompts handles generating prompts
|
||||
func (neo *DSL) handleGeneratePrompts(c *gin.Context) {
|
||||
var content string
|
||||
if c.Request.Method == "GET" {
|
||||
content = c.Query("content")
|
||||
} else {
|
||||
var body struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
if err := c.BindJSON(&body); err != nil {
|
||||
// For SSE requests, send error message in SSE format
|
||||
if strings.Contains(c.GetHeader("Accept"), "text/event-stream") {
|
||||
c.Header("Content-Type", "text/event-stream;charset=utf-8")
|
||||
c.Header("Cache-Control", "no-cache")
|
||||
c.Header("Connection", "keep-alive")
|
||||
msg := message.New().Error("invalid request body").Done()
|
||||
msg.Write(c.Writer)
|
||||
return
|
||||
}
|
||||
c.JSON(400, gin.H{"message": "invalid request body", "code": 400})
|
||||
return
|
||||
}
|
||||
content = body.Content
|
||||
// Set headers for SSE
|
||||
c.Header("Content-Type", "text/event-stream;charset=utf-8")
|
||||
c.Header("Cache-Control", "no-cache")
|
||||
c.Header("Connection", "keep-alive")
|
||||
|
||||
sid := c.GetString("__sid")
|
||||
if sid == "" {
|
||||
sid = uuid.New().String()
|
||||
}
|
||||
|
||||
resp := &generateResponse{
|
||||
c: c,
|
||||
sid: c.GetString("__sid"),
|
||||
content: content,
|
||||
}
|
||||
|
||||
// For SSE requests, set headers before validation
|
||||
if strings.Contains(c.GetHeader("Accept"), "text/event-stream") {
|
||||
c.Header("Content-Type", "text/event-stream;charset=utf-8")
|
||||
c.Header("Cache-Control", "no-cache")
|
||||
c.Header("Connection", "keep-alive")
|
||||
}
|
||||
|
||||
if !resp.validate() {
|
||||
content := c.Query("content")
|
||||
if content == "" {
|
||||
msg := message.New().Error("content is required").Done()
|
||||
msg.Write(c.Writer)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := chatctx.NewWithCancel(resp.sid, c.Query("chat_id"), "")
|
||||
chatID := fmt.Sprintf("generate_prompts_%d", time.Now().UnixNano())
|
||||
|
||||
// Set the context with validated chat_id
|
||||
ctx, cancel := chatctx.NewWithCancel(sid, chatID, c.Query("context"))
|
||||
defer cancel()
|
||||
defer ctx.Release() // Release the context after the request is done
|
||||
|
||||
// Use silent mode for regular HTTP requests, streaming for SSE
|
||||
silent := !strings.Contains(c.GetHeader("Accept"), "text/event-stream")
|
||||
resp.result, resp.err = neo.GeneratePrompts(ctx, resp.content, c, silent)
|
||||
resp.send("result")
|
||||
}
|
||||
// Set the assistant ID
|
||||
ctx = chatctx.WithHistoryVisible(ctx, false)
|
||||
ctx = chatctx.WithAssistantID(ctx, neo.Use.Prompt)
|
||||
err := neo.Answer(ctx, content, c)
|
||||
|
||||
// handleGenerateCustom handles generating custom content
|
||||
func (neo *DSL) handleGenerateCustom(c *gin.Context) {
|
||||
var content, genType, systemPrompt string
|
||||
|
||||
if c.Request.Method == "GET" {
|
||||
content = c.Query("content")
|
||||
genType = c.Query("type")
|
||||
systemPrompt = c.Query("system_prompt")
|
||||
} else {
|
||||
var body struct {
|
||||
Content string `json:"content"`
|
||||
Type string `json:"type"`
|
||||
SystemPrompt string `json:"system_prompt"`
|
||||
}
|
||||
if err := c.BindJSON(&body); err != nil {
|
||||
c.JSON(400, gin.H{"message": "invalid request body", "code": 400})
|
||||
return
|
||||
}
|
||||
content = body.Content
|
||||
genType = body.Type
|
||||
systemPrompt = body.SystemPrompt
|
||||
}
|
||||
|
||||
resp := &generateResponse{
|
||||
c: c,
|
||||
sid: c.GetString("__sid"),
|
||||
content: content,
|
||||
}
|
||||
if !resp.validate() {
|
||||
// Error handling
|
||||
if err != nil {
|
||||
message.New().Done().Error(err).Write(c.Writer)
|
||||
c.Done()
|
||||
return
|
||||
}
|
||||
|
||||
// Additional validations for custom generation
|
||||
if genType == "" {
|
||||
c.JSON(400, gin.H{"message": "type is required", "code": 400})
|
||||
return
|
||||
}
|
||||
if systemPrompt == "" {
|
||||
c.JSON(400, gin.H{"message": "system_prompt is required", "code": 400})
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := chatctx.NewWithCancel(resp.sid, c.Query("chat_id"), "")
|
||||
defer cancel()
|
||||
|
||||
// Use silent mode for regular HTTP requests, streaming for SSE
|
||||
silent := !strings.Contains(c.GetHeader("Accept"), "text/event-stream")
|
||||
resp.result, resp.err = neo.GenerateWithAI(ctx, resp.content, genType, systemPrompt, c, silent)
|
||||
resp.send("result")
|
||||
}
|
||||
|
||||
// handleAssistantList handles listing assistants
|
||||
|
|
|
|||
|
|
@ -242,7 +242,8 @@ func (obj *objectCall) run(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
|||
chatCtx.AssistantID = assistantID
|
||||
chatCtx.ChatID = fmt.Sprintf("call_%s", uuid.New().String()) // New chat id
|
||||
chatCtx.Silent = options.Silent
|
||||
chatCtx.Args = goArgs // Arguments for call
|
||||
chatCtx.ClientType = chatctx.ClientTypeAgent // Set the client type to agent
|
||||
chatCtx.Args = goArgs // Arguments for call
|
||||
|
||||
// Define the callback function
|
||||
var cb func(msg *chatMessage.Message) = nil
|
||||
|
|
|
|||
|
|
@ -12,24 +12,26 @@ import (
|
|||
// Context the context
|
||||
type Context struct {
|
||||
context.Context
|
||||
Sid string `json:"sid" yaml:"-"` // Session ID
|
||||
ChatID string `json:"chat_id,omitempty"` // Chat ID, use to select chat
|
||||
AssistantID string `json:"assistant_id,omitempty"` // Assistant ID, use to select assistant
|
||||
Stack string `json:"stack,omitempty"` // will be removed in the future
|
||||
Path string `json:"pathname,omitempty"` // wiil be rename to path
|
||||
FormData map[string]interface{} `json:"formdata,omitempty"`
|
||||
Field *Field `json:"field,omitempty"`
|
||||
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
|
||||
RetryTimes uint8 `json:"retry_times,omitempty"` // Retry times
|
||||
Upload *FileUpload `json:"upload,omitempty"`
|
||||
Version bool `json:"version,omitempty"` // Version support
|
||||
RAG bool `json:"rag,omitempty"` // RAG support
|
||||
Args []interface{} `json:"args,omitempty"` // Arguments for call
|
||||
SharedSpace plan.Space `json:"-"` // Shared space
|
||||
Sid string `json:"sid" yaml:"-"` // Session ID
|
||||
ChatID string `json:"chat_id,omitempty"` // Chat ID, use to select chat
|
||||
AssistantID string `json:"assistant_id,omitempty"` // Assistant ID, use to select assistant
|
||||
Stack string `json:"stack,omitempty"` // will be removed in the future
|
||||
Path string `json:"pathname,omitempty"` // wiil be rename to path
|
||||
FormData map[string]interface{} `json:"formdata,omitempty"`
|
||||
Field *Field `json:"field,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
Config map[string]interface{} `json:"config,omitempty"`
|
||||
Signal interface{} `json:"signal,omitempty"`
|
||||
Silent bool `json:"silent,omitempty"` // Silent mode
|
||||
ClientType string `json:"client_type,omitempty"` // The request client type. SDK, Desktop, Web, JSSDK, etc. the default is Web
|
||||
HistoryVisible bool `json:"history_visible,omitempty"` // History visible, default is true, if false, the history will not be displayed in the UI
|
||||
Retry bool `json:"retry,omitempty"` // Retry mode
|
||||
RetryTimes uint8 `json:"retry_times,omitempty"` // Retry times
|
||||
Upload *FileUpload `json:"upload,omitempty"`
|
||||
Version bool `json:"version,omitempty"` // Version support
|
||||
RAG bool `json:"rag,omitempty"` // RAG support
|
||||
Args []interface{} `json:"args,omitempty"` // Arguments for call
|
||||
SharedSpace plan.Space `json:"-"` // Shared space
|
||||
}
|
||||
|
||||
// Field the context field
|
||||
|
|
@ -49,10 +51,59 @@ type FileUpload struct {
|
|||
TempFile string `json:"temp_file,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
// ClientTypeAgent is the client type for Agent
|
||||
ClientTypeAgent = "agent"
|
||||
|
||||
// ClientTypeWeb is the client type for Web (Default UI)
|
||||
ClientTypeWeb = "web"
|
||||
|
||||
// ClientTypeSDK is the client type for SDK
|
||||
ClientTypeSDK = "android"
|
||||
|
||||
// ClientTypeIOS is the client type for IOS
|
||||
ClientTypeIOS = "ios"
|
||||
|
||||
// ClientTypeJSSDK is the client type for JSSDK
|
||||
ClientTypeJSSDK = "jssdk"
|
||||
|
||||
// ClientTypeMacOS is the client type for MacOS Desktop
|
||||
ClientTypeMacOS = "macos"
|
||||
|
||||
// ClientTypeWindows is the client type for Windows Desktop
|
||||
ClientTypeWindows = "windows"
|
||||
|
||||
// ClientTypeLinux is the client type for Linux Desktop
|
||||
ClientTypeLinux = "linux"
|
||||
)
|
||||
|
||||
// SupportedClientTypes is the supported client types
|
||||
var SupportedClientTypes = map[string]bool{
|
||||
ClientTypeAgent: true,
|
||||
ClientTypeWeb: true,
|
||||
ClientTypeSDK: true,
|
||||
ClientTypeIOS: true,
|
||||
ClientTypeJSSDK: true,
|
||||
ClientTypeMacOS: true,
|
||||
ClientTypeWindows: true,
|
||||
ClientTypeLinux: true,
|
||||
}
|
||||
|
||||
// New create a new context
|
||||
func New(sid, cid, payload string) Context {
|
||||
|
||||
ctx := Context{Context: context.Background(), Sid: sid, ChatID: cid, SharedSpace: plan.NewMemorySharedSpace()}
|
||||
// Validate the client type
|
||||
ctx := Context{
|
||||
Context: context.Background(),
|
||||
SharedSpace: plan.NewMemorySharedSpace(),
|
||||
Sid: sid,
|
||||
ChatID: cid,
|
||||
HistoryVisible: true,
|
||||
ClientType: ClientTypeWeb,
|
||||
Silent: false,
|
||||
}
|
||||
|
||||
if payload == "" {
|
||||
return ctx
|
||||
}
|
||||
|
|
@ -77,6 +128,29 @@ func WithAssistantID(ctx Context, assistantID string) Context {
|
|||
return ctx
|
||||
}
|
||||
|
||||
// WithSilent set the silent mode
|
||||
func WithSilent(ctx Context, silent bool) Context {
|
||||
ctx.Silent = silent
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithClientType set the client type
|
||||
func WithClientType(ctx Context, clientType string) Context {
|
||||
// Validate the client type
|
||||
if !SupportedClientTypes[clientType] {
|
||||
log.Error("[Neo] Invalid client type: %s", clientType)
|
||||
return ctx
|
||||
}
|
||||
ctx.ClientType = clientType
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithHistoryVisible set the history visible
|
||||
func WithHistoryVisible(ctx Context, historyVisible bool) Context {
|
||||
ctx.HistoryVisible = historyVisible
|
||||
return ctx
|
||||
}
|
||||
|
||||
// NewWithTimeout create a new context with timeout
|
||||
func NewWithTimeout(sid, cid, payload string, timeout time.Duration) (Context, context.CancelFunc) {
|
||||
ctx := New(sid, cid, payload)
|
||||
|
|
@ -127,6 +201,12 @@ func (ctx *Context) Map() map[string]interface{} {
|
|||
data["silent"] = ctx.Silent
|
||||
}
|
||||
|
||||
// History visible
|
||||
data["history_visible"] = ctx.HistoryVisible
|
||||
|
||||
// Client type
|
||||
data["client_type"] = ctx.ClientType
|
||||
|
||||
// Retry mode
|
||||
if ctx.Retry {
|
||||
data["retry"] = ctx.Retry
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import (
|
|||
func (neo *DSL) HookCreate(ctx chatctx.Context, messages []map[string]interface{}, c *gin.Context) (CreateResponse, error) {
|
||||
|
||||
// Default assistant
|
||||
assistantID := neo.Use
|
||||
assistantID := neo.Use.Default
|
||||
if ctx.AssistantID != "" {
|
||||
assistantID = ctx.AssistantID
|
||||
}
|
||||
|
|
|
|||
19
neo/load.go
19
neo/load.go
|
|
@ -47,6 +47,21 @@ func Load(cfg config.Config) error {
|
|||
setting.StoreSetting.MaxSize = 100
|
||||
}
|
||||
|
||||
// Default Assistant
|
||||
if setting.Use == nil {
|
||||
setting.Use = &Use{Default: "neo"}
|
||||
}
|
||||
|
||||
// Title Assistant
|
||||
if setting.Use.Title == "" {
|
||||
setting.Use.Title = setting.Use.Default
|
||||
}
|
||||
|
||||
// Prompt Assistant
|
||||
if setting.Use.Prompt == "" {
|
||||
setting.Use.Prompt = setting.Use.Default
|
||||
}
|
||||
|
||||
Neo = &setting
|
||||
|
||||
// Store Setting
|
||||
|
|
@ -185,8 +200,8 @@ func initAssistant() error {
|
|||
|
||||
// defaultAssistant get the default assistant
|
||||
func defaultAssistant() (*assistant.Assistant, error) {
|
||||
if Neo.Use != "" {
|
||||
return assistant.Get(Neo.Use)
|
||||
if Neo.Use != nil && Neo.Use.Default != "" {
|
||||
return assistant.Get(Neo.Use.Default)
|
||||
}
|
||||
|
||||
name := Neo.Name
|
||||
|
|
|
|||
233
neo/neo.go
233
neo/neo.go
|
|
@ -6,10 +6,8 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yaoapp/kun/log"
|
||||
"github.com/yaoapp/yao/neo/assistant"
|
||||
chatctx "github.com/yaoapp/yao/neo/context"
|
||||
"github.com/yaoapp/yao/neo/message"
|
||||
)
|
||||
|
||||
// Answer reply the message
|
||||
|
|
@ -34,237 +32,6 @@ func (neo *DSL) Select(id string) (assistant.API, error) {
|
|||
return assistant.Get(id)
|
||||
}
|
||||
|
||||
// GeneratePrompts generate prompts for the AI assistant
|
||||
func (neo *DSL) GeneratePrompts(ctx chatctx.Context, input string, c *gin.Context, silent ...bool) (string, error) {
|
||||
prompts := `
|
||||
Optimize the prompts for the AI assistant
|
||||
1. Optimize prompts based on the user's input
|
||||
2. The prompts should be clear and specific
|
||||
3. The prompts should be in the same language as the input
|
||||
4. Keep the prompts concise but comprehensive
|
||||
5. DO NOT ASK USER FOR MORE INFORMATION, JUST GENERATE PROMPTS
|
||||
6. DO NOT ANSWER THE QUESTION, JUST GENERATE PROMPTS
|
||||
`
|
||||
isSilent := false
|
||||
if len(silent) > 0 {
|
||||
isSilent = silent[0]
|
||||
}
|
||||
return neo.GenerateWithAI(ctx, input, "prompts", prompts, c, isSilent)
|
||||
}
|
||||
|
||||
// GenerateChatTitle generate the chat title
|
||||
func (neo *DSL) GenerateChatTitle(ctx chatctx.Context, input string, c *gin.Context, silent ...bool) (string, error) {
|
||||
prompts := `
|
||||
Help me generate a title for the chat
|
||||
1. The title should be a short and concise description of the chat.
|
||||
2. The title should be a single sentence.
|
||||
3. The title should be in same language as the chat.
|
||||
4. The title should be no more than 50 characters.
|
||||
5. ANSWER ONLY THE TITLE CONTENT, FOR EXAMPLE: Chat with AI is a valid title, but "Chat with AI" is not a valid title.
|
||||
`
|
||||
isSilent := false
|
||||
if len(silent) > 0 {
|
||||
isSilent = silent[0]
|
||||
}
|
||||
return neo.GenerateWithAI(ctx, input, "title", prompts, c, isSilent)
|
||||
}
|
||||
|
||||
// GenerateWithAI generate content with AI, type can be "title", "prompts", etc.
|
||||
func (neo *DSL) GenerateWithAI(ctx chatctx.Context, input string, messageType string, systemPrompt string, c *gin.Context, silent bool) (string, error) {
|
||||
messages := []map[string]interface{}{
|
||||
{"role": "system", "content": systemPrompt},
|
||||
{
|
||||
"role": "user",
|
||||
"content": input,
|
||||
"type": messageType,
|
||||
"name": ctx.Sid,
|
||||
},
|
||||
}
|
||||
|
||||
res, err := neo.HookCreate(ctx, messages, c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Select Assistant
|
||||
ast, err := neo.Select(res.AssistantID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if ast == nil {
|
||||
msg := message.New().Error("assistant is not initialized").Done()
|
||||
msg.Write(c.Writer)
|
||||
return "", fmt.Errorf("assistant is not initialized")
|
||||
}
|
||||
|
||||
clientBreak := make(chan bool, 1)
|
||||
done := make(chan bool, 1)
|
||||
fail := make(chan error, 1)
|
||||
contents := message.NewContents()
|
||||
|
||||
// Chat with AI in background
|
||||
go func() {
|
||||
msgList := []message.Message{}
|
||||
for _, vv := range messages {
|
||||
msg := message.New().Map(vv)
|
||||
if content, ok := vv["content"].(string); ok {
|
||||
msgs, err := message.NewContent(content)
|
||||
if err == nil {
|
||||
for _, v := range msgs {
|
||||
v.AssistantID = msg.AssistantID
|
||||
v.AssistantName = msg.AssistantName
|
||||
v.AssistantAvatar = msg.AssistantAvatar
|
||||
v.Role = msg.Role
|
||||
v.Name = msg.Name
|
||||
v.Mentions = msg.Mentions
|
||||
msgList = append(msgList, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errorRaw := ""
|
||||
isFirstThink := true
|
||||
isThinking := false
|
||||
currentMessageID := ""
|
||||
tokenID := ""
|
||||
beganAt := int64(0)
|
||||
err := ast.Chat(c.Request.Context(), msgList, neo.Option, func(data []byte) int {
|
||||
select {
|
||||
case <-clientBreak:
|
||||
return 0 // break
|
||||
|
||||
default:
|
||||
msg := message.NewOpenAI(data, isThinking)
|
||||
if msg == nil {
|
||||
return 1 // continue
|
||||
}
|
||||
|
||||
if msg.Pending {
|
||||
errorRaw += msg.Text
|
||||
return 1 // continue
|
||||
}
|
||||
|
||||
// Handle error
|
||||
if msg.Type == "error" {
|
||||
fail <- fmt.Errorf("%s", msg.Text)
|
||||
return 0 // break
|
||||
}
|
||||
|
||||
// for api reasoning_content response
|
||||
if msg.Type == "think" {
|
||||
if isFirstThink {
|
||||
msg.Text = "<think>\n" + msg.Text // add the think begin tag
|
||||
isFirstThink = false
|
||||
isThinking = true
|
||||
}
|
||||
}
|
||||
|
||||
// for api reasoning_content response
|
||||
if isThinking && msg.Type != "think" {
|
||||
// add the think close tag
|
||||
end := message.New().Map(map[string]interface{}{"text": "\n</think>\n", "type": "think", "delta": true})
|
||||
end.Write(c.Writer)
|
||||
end.ID = currentMessageID
|
||||
end.AppendTo(contents)
|
||||
isThinking = false
|
||||
|
||||
// Clear the token and make a new line
|
||||
contents.NewText([]byte{}, message.Extra{ID: currentMessageID})
|
||||
contents.ClearToken(currentMessageID)
|
||||
}
|
||||
|
||||
// Append content and send message
|
||||
msg.AppendTo(contents)
|
||||
|
||||
// Scan the tokens
|
||||
contents.ScanTokens(currentMessageID, tokenID, beganAt, func(params message.ScanCallbackParams) {
|
||||
currentMessageID = params.MessageID
|
||||
msg.ID = params.MessageID
|
||||
msg.Type = params.Token
|
||||
msg.Text = "" // clear the text
|
||||
msg.Props = map[string]interface{}{"text": params.Text} // Update props
|
||||
|
||||
// End of the token clear the text
|
||||
if params.Begin {
|
||||
msg.Begin = beganAt
|
||||
return
|
||||
}
|
||||
|
||||
// End of the token clear the text
|
||||
if params.End {
|
||||
msg.End = params.EndAt
|
||||
return
|
||||
}
|
||||
|
||||
// New message with the tails
|
||||
if params.Tails != "" {
|
||||
newMsg, err := message.NewString(params.Tails, params.MessageID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
msgList = append(msgList, *newMsg)
|
||||
}
|
||||
})
|
||||
|
||||
if !silent {
|
||||
value := msg.String()
|
||||
if value != "" {
|
||||
message.New().
|
||||
Map(map[string]interface{}{
|
||||
"text": value,
|
||||
"delta": true,
|
||||
"done": msg.IsDone,
|
||||
}).
|
||||
Write(c.Writer)
|
||||
}
|
||||
}
|
||||
|
||||
// Complete the stream
|
||||
if msg.IsDone {
|
||||
value := msg.String()
|
||||
if value == "" {
|
||||
msg.Write(c.Writer)
|
||||
}
|
||||
done <- true
|
||||
return 0 // break
|
||||
}
|
||||
|
||||
return 1 // continue
|
||||
}
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Error("Chat error: %s", err.Error())
|
||||
if !silent {
|
||||
message.New().Error(err).Done().Write(c.Writer)
|
||||
}
|
||||
}
|
||||
|
||||
if errorRaw != "" {
|
||||
msg, err := message.NewStringError(errorRaw)
|
||||
if err != nil {
|
||||
log.Error("Error parsing error message: %s", err.Error())
|
||||
}
|
||||
msg.Write(c.Writer)
|
||||
}
|
||||
|
||||
done <- true
|
||||
}()
|
||||
|
||||
// Wait for completion or client disconnect
|
||||
select {
|
||||
case <-done:
|
||||
return contents.Text(), nil
|
||||
case err := <-fail:
|
||||
return "", err
|
||||
case <-c.Writer.CloseNotify():
|
||||
clientBreak <- true
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
// Upload upload a file
|
||||
func (neo *DSL) Upload(ctx chatctx.Context, c *gin.Context) (*assistant.File, error) {
|
||||
// Get the file
|
||||
|
|
|
|||
|
|
@ -607,6 +607,7 @@ func (conv *Xun) SaveHistory(sid string, messages []map[string]interface{}, cid
|
|||
|
||||
// Get silent flag from context
|
||||
var silent bool = false
|
||||
var historyVisible bool = true
|
||||
if context != nil {
|
||||
if silentVal, ok := context["silent"]; ok {
|
||||
switch v := silentVal.(type) {
|
||||
|
|
@ -620,6 +621,20 @@ func (conv *Xun) SaveHistory(sid string, messages []map[string]interface{}, cid
|
|||
silent = v != 0
|
||||
}
|
||||
}
|
||||
|
||||
// Get history visible from context
|
||||
if historyVisibleVal, ok := context["history_visible"]; ok {
|
||||
switch v := historyVisibleVal.(type) {
|
||||
case bool:
|
||||
historyVisible = v
|
||||
case string:
|
||||
historyVisible = v == "true" || v == "1" || v == "yes"
|
||||
case int:
|
||||
historyVisible = v != 0
|
||||
case float64:
|
||||
historyVisible = v != 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// First ensure chat record exists
|
||||
|
|
@ -639,7 +654,7 @@ func (conv *Xun) SaveHistory(sid string, messages []map[string]interface{}, cid
|
|||
"chat_id": cid,
|
||||
"sid": userID,
|
||||
"assistant_id": assistantID,
|
||||
"silent": silent,
|
||||
"silent": silent || historyVisible == false,
|
||||
"created_at": time.Now(),
|
||||
})
|
||||
|
||||
|
|
@ -653,7 +668,7 @@ func (conv *Xun) SaveHistory(sid string, messages []map[string]interface{}, cid
|
|||
Where("sid", userID).
|
||||
Update(map[string]interface{}{
|
||||
"assistant_id": assistantID,
|
||||
"silent": silent,
|
||||
"silent": silent || historyVisible == false,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import (
|
|||
type DSL struct {
|
||||
ID string `json:"-" yaml:"-"`
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
Use string `json:"use,omitempty" yaml:"use,omitempty"` // Which assistant to use default
|
||||
Use *Use `json:"use,omitempty" yaml:"use,omitempty"` // Which assistant to use default, title, prompt
|
||||
Guard string `json:"guard,omitempty" yaml:"guard,omitempty"`
|
||||
Connector string `json:"connector" yaml:"connector"`
|
||||
StoreSetting store.Setting `json:"store" yaml:"store"`
|
||||
|
|
@ -34,6 +34,13 @@ type DSL struct {
|
|||
GuardHandlers []gin.HandlerFunc `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// Use the use setting for the assistant
|
||||
type Use struct {
|
||||
Default string `json:"default,omitempty" yaml:"default,omitempty"`
|
||||
Title string `json:"title,omitempty" yaml:"title,omitempty"`
|
||||
Prompt string `json:"prompt,omitempty" yaml:"prompt,omitempty"`
|
||||
}
|
||||
|
||||
// VisionSetting the vision setting
|
||||
type VisionSetting struct {
|
||||
Storage driver.StorageConfig `json:"storage" yaml:"storage"`
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ class Agent {
|
|||
private chat_id?: string;
|
||||
private es: EventSource | null;
|
||||
private context: Record<string, any>;
|
||||
private silent?: boolean = false;
|
||||
private history_visible?: boolean = false;
|
||||
|
||||
/**
|
||||
* Agent constructor
|
||||
|
|
@ -72,6 +74,28 @@ class Agent {
|
|||
this.chat_id = option.chat_id;
|
||||
this.es = null;
|
||||
this.context = option.context || {};
|
||||
|
||||
// Set silent mode, default is true
|
||||
if (option.silent !== undefined) {
|
||||
this.silent =
|
||||
option.silent === true ||
|
||||
option.silent === "true" ||
|
||||
option.silent === 1 ||
|
||||
option.silent === "1"
|
||||
? false
|
||||
: true;
|
||||
}
|
||||
|
||||
// Set history visible mode, default is false
|
||||
if (option.history_visible !== undefined) {
|
||||
this.history_visible =
|
||||
option.history_visible === true ||
|
||||
option.history_visible === "true" ||
|
||||
option.history_visible === 1 ||
|
||||
option.history_visible === "1"
|
||||
? true
|
||||
: false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -152,10 +176,12 @@ class Agent {
|
|||
const contentRaw = encodeURIComponent(JSON.stringify(content));
|
||||
const contextRaw = encodeURIComponent(JSON.stringify(context));
|
||||
const token = this.token;
|
||||
const silent = this.silent ? "true" : "false";
|
||||
const history_visible = this.history_visible ? "true" : "false";
|
||||
const chatId = this.chat_id || this.makeChatID();
|
||||
const assistantParam = `&assistant_id=${this.assistant_id}`;
|
||||
const status_endpoint = `${this.host}/status?content=${contentRaw}&context=${contextRaw}&token=${token}&chat_id=${chatId}${assistantParam}`;
|
||||
const endpoint = `${this.host}?content=${contentRaw}&context=${contextRaw}&token=${token}&chat_id=${chatId}${assistantParam}`;
|
||||
const endpoint = `${this.host}?client_type=jssdk&content=${contentRaw}&context=${contextRaw}&token=${token}&silent=${silent}&history_visible=${history_visible}&chat_id=${chatId}${assistantParam}`;
|
||||
|
||||
const handleError = async (error: any) => {
|
||||
try {
|
||||
|
|
@ -447,6 +473,8 @@ type AgentInput =
|
|||
interface AgentOption {
|
||||
host?: string;
|
||||
token: string;
|
||||
silent?: boolean | string | number;
|
||||
history_visible?: boolean | string | number;
|
||||
chat_id?: string;
|
||||
context?: Record<string, any>;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue