From bc3dd567b59c05429901895183ecd5f43bb13630 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 24 Nov 2025 18:34:00 +0800 Subject: [PATCH] Refactor assistant settings to use 'Uses' instead of 'Use' for improved clarity - Updated the Load function and API handlers to replace 'Use' with 'Uses' for assistant configuration, enhancing consistency across the codebase. - Modified related structures and functions to reflect the new naming convention, ensuring better alignment with the intended functionality. - Added a new Skip struct to manage request skip configurations, improving request handling flexibility. - Implemented tests for the GetSkip function to validate skip parameter extraction from both request body and query parameters. --- agent/api/api.go | 20 ++-- agent/context/openapi.go | 25 +++++ agent/context/openapi_test.go | 205 ++++++++++++++++++++++++++++++++++ agent/context/types.go | 22 ++-- agent/load.go | 26 ++--- agent/types/types.go | 10 +- widgets/app/app.go | 12 +- 7 files changed, 282 insertions(+), 38 deletions(-) diff --git a/agent/api/api.go b/agent/api/api.go index cd1b302d..b9bfdae4 100644 --- a/agent/api/api.go +++ b/agent/api/api.go @@ -397,7 +397,7 @@ func (agent *API) handleChatLatest(c *gin.Context) { // Create a new chat if len(chats.Groups) == 0 || len(chats.Groups[0].Chats) == 0 { - assistantID := agent.Use.Default + assistantID := agent.Uses.Default queryAssistantID := c.Query("assistant_id") if queryAssistantID != "" { assistantID = queryAssistantID @@ -416,7 +416,7 @@ func (agent *API) handleChatLatest(c *gin.Context) { "assistant_id": ast.ID, "assistant_name": ast.GetName(locale), "assistant_avatar": ast.Avatar, - "assistant_deleteable": agent.Use.Default != ast.ID, + "assistant_deleteable": agent.Uses.Default != ast.ID, }}) c.Done() return @@ -439,10 +439,10 @@ func (agent *API) handleChatLatest(c *gin.Context) { // assistant_id is nil return the default assistant if chat.Chat["assistant_id"] == nil { - chat.Chat["assistant_id"] = agent.Use.Default + chat.Chat["assistant_id"] = agent.Uses.Default // Get the assistant info - ast, err := assistant.Get(agent.Use.Default) + ast, err := assistant.Get(agent.Uses.Default) if err != nil { c.JSON(500, gin.H{"message": err.Error(), "code": 500}) c.Done() @@ -452,7 +452,7 @@ func (agent *API) handleChatLatest(c *gin.Context) { chat.Chat["assistant_avatar"] = ast.Avatar } - chat.Chat["assistant_deleteable"] = agent.Use.Default != chat.Chat["assistant_id"] + chat.Chat["assistant_deleteable"] = agent.Uses.Default != chat.Chat["assistant_id"] c.JSON(200, map[string]interface{}{"data": chat}) c.Done() } @@ -488,10 +488,10 @@ func (agent *API) handleChatDetail(c *gin.Context) { // assistant_id is nil return the default assistant if chat.Chat["assistant_id"] == nil { - chat.Chat["assistant_id"] = agent.Use.Default + chat.Chat["assistant_id"] = agent.Uses.Default // Get the assistant info - ast, err := assistant.Get(agent.Use.Default) + ast, err := assistant.Get(agent.Uses.Default) if err != nil { c.JSON(500, gin.H{"message": err.Error(), "code": 500}) c.Done() @@ -501,7 +501,7 @@ func (agent *API) handleChatDetail(c *gin.Context) { chat.Chat["assistant_avatar"] = ast.Avatar } - chat.Chat["assistant_deleteable"] = agent.Use.Default != chat.Chat["assistant_id"] + chat.Chat["assistant_deleteable"] = agent.Uses.Default != chat.Chat["assistant_id"] c.JSON(200, map[string]interface{}{"data": chat}) c.Done() } @@ -674,7 +674,7 @@ func (agent *API) handleGenerateTitle(c *gin.Context) { // // Set the assistant ID // ctx = chatctx.WithHistoryVisible(ctx, false) - // ctx = chatctx.WithAssistantID(ctx, agent.Use.Title) + // ctx = chatctx.WithAssistantID(ctx, agent.Uses.Title) err := agent.Answer(ctx, content, c) @@ -714,7 +714,7 @@ func (agent *API) handleGeneratePrompts(c *gin.Context) { // // Set the assistant ID // ctx = chatctx.WithHistoryVisible(ctx, false) - // ctx = chatctx.WithAssistantID(ctx, agent.Use.Prompt) + // ctx = chatctx.WithAssistantID(ctx, agent.Uses.Prompt) err := agent.Answer(ctx, content, c) // Error handling diff --git a/agent/context/openapi.go b/agent/context/openapi.go index 6e70e238..d6f5343f 100644 --- a/agent/context/openapi.go +++ b/agent/context/openapi.go @@ -65,6 +65,7 @@ func GetCompletionRequest(c *gin.Context, cache store.Store) (*CompletionRequest }, Route: GetRoute(c, completionReq), Metadata: GetMetadata(c, completionReq), + Skip: GetSkip(c, completionReq), } // Initialize interrupt controller @@ -360,6 +361,30 @@ func GetRoute(c *gin.Context, req *CompletionRequest) string { return "" } +// GetSkip extracts skip configuration from request with priority: +// 1. CompletionRequest.Skip (from payload body) - Priority +// 2. Individual query parameters: "skip_history", "skip_trace" +func GetSkip(c *gin.Context, req *CompletionRequest) *Skip { + // Priority 1: From CompletionRequest body (most direct) + if req != nil && req.Skip != nil { + return req.Skip + } + + // Priority 2: Individual query parameters (recommended for query usage) + skipHistory := c.Query("skip_history") == "true" || c.Query("skip_history") == "1" + skipTrace := c.Query("skip_trace") == "true" || c.Query("skip_trace") == "1" + + // Check if any skip parameter is set + if c.Query("skip_history") != "" || c.Query("skip_trace") != "" { + return &Skip{ + History: skipHistory, + Trace: skipTrace, + } + } + + return nil +} + // GetMetadata extracts metadata from request with priority: // 1. Query parameter "metadata" (JSON string) // 2. Header "X-Yao-Metadata" (Base64 encoded JSON string) diff --git a/agent/context/openapi_test.go b/agent/context/openapi_test.go index 89afa285..88a50474 100644 --- a/agent/context/openapi_test.go +++ b/agent/context/openapi_test.go @@ -930,3 +930,208 @@ func TestGetCompletionRequest_ChatIDFallback(t *testing.T) { t.Errorf("Expected ChatID to be at least 8 characters, got %d", len(ctx.ChatID)) } } + +func TestGetSkip_FromBody(t *testing.T) { + gin.SetMode(gin.TestMode) + + req := httptest.NewRequest("POST", "/chat/completions", nil) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Request = req + + completionReq := &CompletionRequest{ + Skip: &Skip{ + History: true, + Trace: false, + }, + } + + skip := GetSkip(c, completionReq) + if skip == nil { + t.Fatal("Expected skip to be returned") + } + + if !skip.History { + t.Error("Expected skip.History to be true") + } + + if skip.Trace { + t.Error("Expected skip.Trace to be false") + } +} + +func TestGetSkip_FromQueryParams(t *testing.T) { + gin.SetMode(gin.TestMode) + + req := httptest.NewRequest("GET", "/chat/completions?skip_history=true&skip_trace=false", nil) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Request = req + + skip := GetSkip(c, nil) + if skip == nil { + t.Fatal("Expected skip to be returned") + } + + if !skip.History { + t.Error("Expected skip.History to be true from query param") + } + + if skip.Trace { + t.Error("Expected skip.Trace to be false") + } +} + +func TestGetSkip_FromQueryParams_ShortForm(t *testing.T) { + gin.SetMode(gin.TestMode) + + req := httptest.NewRequest("GET", "/chat/completions?skip_history=1&skip_trace=1", nil) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Request = req + + skip := GetSkip(c, nil) + if skip == nil { + t.Fatal("Expected skip to be returned") + } + + if !skip.History { + t.Error("Expected skip.History to be true from query param (1)") + } + + if !skip.Trace { + t.Error("Expected skip.Trace to be true from query param (1)") + } +} + +func TestGetSkip_Priority(t *testing.T) { + gin.SetMode(gin.TestMode) + + // Body should take priority over query + req := httptest.NewRequest("POST", "/chat/completions?skip_history=false&skip_trace=false", nil) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Request = req + + completionReq := &CompletionRequest{ + Skip: &Skip{ + History: true, + Trace: true, + }, + } + + skip := GetSkip(c, completionReq) + if skip == nil { + t.Fatal("Expected skip to be returned") + } + + // Body should take priority + if !skip.History { + t.Error("Expected body parameter to take priority, skip.History should be true") + } + + if !skip.Trace { + t.Error("Expected body parameter to take priority, skip.Trace should be true") + } +} + +func TestGetSkip_Nil(t *testing.T) { + gin.SetMode(gin.TestMode) + + req := httptest.NewRequest("GET", "/chat/completions", nil) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Request = req + + skip := GetSkip(c, nil) + if skip != nil { + t.Errorf("Expected skip to be nil, got %v", skip) + } +} + +func TestGetSkip_OnlyHistorySet(t *testing.T) { + gin.SetMode(gin.TestMode) + + req := httptest.NewRequest("GET", "/chat/completions?skip_history=true", nil) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Request = req + + skip := GetSkip(c, nil) + if skip == nil { + t.Fatal("Expected skip to be returned") + } + + if !skip.History { + t.Error("Expected skip.History to be true") + } + + if skip.Trace { + t.Error("Expected skip.Trace to be false (default)") + } +} + +func TestGetSkip_FromBodyViaParseRequest(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + gin.SetMode(gin.TestMode) + + // Test parsing Skip from full request body + messages := []Message{ + { + Role: RoleUser, + Content: "Generate a title for this chat", + }, + } + + requestBody := map[string]interface{}{ + "model": "workers.system.title-yao_test", + "messages": messages, + "skip": map[string]interface{}{ + "history": true, + "trace": false, + }, + } + + bodyBytes, _ := json.Marshal(requestBody) + + req := httptest.NewRequest("POST", "/chat/completions", bytes.NewBuffer(bodyBytes)) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Request = req + + // Parse the request + completionReq, err := parseCompletionRequestData(c) + if err != nil { + t.Fatalf("Failed to parse completion request: %v", err) + } + + // Verify Skip was parsed correctly + if completionReq.Skip == nil { + t.Fatal("Expected Skip to be parsed from body, got nil") + } + + if !completionReq.Skip.History { + t.Error("Expected Skip.History to be true from body") + } + + if completionReq.Skip.Trace { + t.Error("Expected Skip.Trace to be false from body") + } + + // Now test GetSkip function with the parsed request + skip := GetSkip(c, completionReq) + if skip == nil { + t.Fatal("Expected GetSkip to return skip configuration") + } + + if !skip.History { + t.Error("Expected GetSkip to return History=true") + } + + if skip.Trace { + t.Error("Expected GetSkip to return Trace=false") + } +} diff --git a/agent/context/types.go b/agent/context/types.go index 65ad94f1..e01e607e 100644 --- a/agent/context/types.go +++ b/agent/context/types.go @@ -186,18 +186,25 @@ type AssistantInfo struct { Description string `json:"description,omitempty"` // Assistant Description } +// Skip configuration for what to skip in this request +type Skip struct { + History bool `json:"history"` // Skip saving chat history (for internal calls like title/prompt generation) + Trace bool `json:"trace"` // Skip trace logging +} + // Context the context type Context struct { // Context context.Context - ID string `json:"id"` // Context ID for external interrupt identification - Space plan.Space `json:"-"` // Shared data space, it will be used to share data between the request and the call - Cache store.Store `json:"-"` // Cache store, it will be used to store the message cache, default is "__yao.agent.cache" - Stack *Stack `json:"-"` // Stack, current active stack of the request - Stacks map[string]*Stack `json:"-"` // Stacks, all stacks in this request (for trace logging) - Writer Writer `json:"-"` // Writer, it will be used to write response data to the client - trace traceTypes.Manager `json:"-"` // Trace manager, lazy initialized on first access + ID string `json:"id"` // Context ID for external interrupt identification + Space plan.Space `json:"-"` // Shared data space, it will be used to share data between the request and the call + Cache store.Store `json:"-"` // Cache store, it will be used to store the message cache, default is "__yao.agent.cache" + Stack *Stack `json:"-"` // Stack, current active stack of the request + Stacks map[string]*Stack `json:"-"` // Stacks, all stacks in this request (for trace logging) + Writer Writer `json:"-"` // Writer, it will be used to write response data to the client + Skip *Skip `json:"skip,omitempty"` // Skip configuration (history, trace, etc.), nil means don't skip anything + trace traceTypes.Manager `json:"-"` // Trace manager, lazy initialized on first access // Model capabilities (set by assistant, used by output adapters) Capabilities *ModelCapabilities `json:"-"` // Model capabilities for the current connector @@ -443,6 +450,7 @@ type CompletionRequest struct { // CUI Context information Route string `json:"route,omitempty"` // Optional: route of the request for CUI context Metadata map[string]interface{} `json:"metadata,omitempty"` // Optional: metadata to pass to the page for CUI context + Skip *Skip `json:"skip,omitempty"` // Optional: skip configuration (history, trace, etc.) } // AudioConfig represents the audio output configuration for models that support audio diff --git a/agent/load.go b/agent/load.go index e0a8d225..9fa9bab4 100644 --- a/agent/load.go +++ b/agent/load.go @@ -45,18 +45,18 @@ func Load(cfg config.Config) error { } // Default Assistant, Agent is the developer name, Mohe is the brand name of the assistant - if setting.Use == nil { - setting.Use = &types.Use{Default: "mohe"} // Agent is the developer name, Mohe is the brand name of the assistant + if setting.Uses == nil { + setting.Uses = &types.Uses{Default: "mohe"} // Agent is the developer name, Mohe is the brand name of the assistant } // Title Assistant - if setting.Use.Title == "" { - setting.Use.Title = setting.Use.Default + if setting.Uses.Title == "" { + setting.Uses.Title = setting.Uses.Default } // Prompt Assistant - if setting.Use.Prompt == "" { - setting.Use.Prompt = setting.Use.Default + if setting.Uses.Prompt == "" { + setting.Uses.Prompt = setting.Uses.Default } // Initialize Agent API @@ -173,12 +173,12 @@ func initAssistant() error { } // Set global Uses configuration - if api.Agent.DSL.Use != nil { + if api.Agent.DSL.Uses != nil { globalUses := &context.Uses{ - Vision: api.Agent.DSL.Use.Vision, - Audio: api.Agent.DSL.Use.Audio, - Search: api.Agent.DSL.Use.Search, - Fetch: api.Agent.DSL.Use.Fetch, + Vision: api.Agent.DSL.Uses.Vision, + Audio: api.Agent.DSL.Uses.Audio, + Search: api.Agent.DSL.Uses.Search, + Fetch: api.Agent.DSL.Uses.Fetch, } assistant.SetGlobalUses(globalUses) } @@ -205,8 +205,8 @@ func initAssistant() error { // defaultAssistant get the default assistant func defaultAssistant() (*assistant.Assistant, error) { - if api.Agent.DSL.Use == nil || api.Agent.DSL.Use.Default == "" { + if api.Agent.DSL.Uses == nil || api.Agent.DSL.Uses.Default == "" { return nil, fmt.Errorf("default assistant not found") } - return assistant.Get(api.Agent.DSL.Use.Default) + return assistant.Get(api.Agent.DSL.Uses.Default) } diff --git a/agent/types/types.go b/agent/types/types.go index b0e4047d..85e7ac42 100644 --- a/agent/types/types.go +++ b/agent/types/types.go @@ -12,9 +12,9 @@ type DSL struct { // Agent Global Settings // =============================== - Use *Use `json:"use,omitempty" yaml:"use,omitempty"` // Which assistant to use default, title, prompt - StoreSetting store.Setting `json:"store" yaml:"store"` // The store setting of the assistant - Cache string `json:"cache" yaml:"cache"` // The cache store of the assistant, if not set, default is "__yao.agent.cache" + Uses *Uses `json:"uses,omitempty" yaml:"uses,omitempty"` // Which assistant to use default, title, prompt + StoreSetting store.Setting `json:"store" yaml:"store"` // The store setting of the assistant + Cache string `json:"cache" yaml:"cache"` // The cache store of the assistant, if not set, default is "__yao.agent.cache" // AuthSetting *Auth `json:"auth,omitempty" yaml:"auth,omitempty"` // Authenticate Settings // UploadSetting *Upload `json:"upload,omitempty" yaml:"upload,omitempty"` // Upload Settings @@ -38,9 +38,9 @@ type DSL struct { GuardHandlers []gin.HandlerFunc `json:"-" yaml:"-"` } -// Use the default assistant settings +// Uses the default assistant settings // =============================== -type Use struct { +type Uses struct { Default string `json:"default,omitempty" yaml:"default,omitempty"` // The default assistant to use Title string `json:"title,omitempty" yaml:"title,omitempty"` // The assistant for generating the topic title. Prompt string `json:"prompt,omitempty" yaml:"prompt,omitempty"` // The assistant for generating the prompt. diff --git a/widgets/app/app.go b/widgets/app/app.go index 811feb0b..2c5cb311 100644 --- a/widgets/app/app.go +++ b/widgets/app/app.go @@ -12,7 +12,6 @@ import ( jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou/api" "github.com/yaoapp/gou/application" - "github.com/yaoapp/gou/connector" "github.com/yaoapp/gou/process" v8 "github.com/yaoapp/gou/runtime/v8" "github.com/yaoapp/gou/session" @@ -552,6 +551,13 @@ func processXgen(process *process.Process) interface{} { // The default assistant agentConfig := map[string]interface{}{} if agent.Agent != nil { + + // Add Uses Settings + if agent.Agent.DSL != nil && agent.Agent.DSL.Uses != nil { + agentConfig["uses"] = agent.Agent.DSL.Uses + } + + // Add Default Assistant Settings ( Will be removed later ) if ast, ok := agent.Agent.Assistant.(*assistant.Assistant); ok { agentConfig["default"] = map[string]interface{}{ "assistant_id": ast.ID, @@ -562,8 +568,8 @@ func processXgen(process *process.Process) interface{} { } } - // Available connectors - agentConfig["connectors"] = connector.AIConnectors + // Available connectors Removed later, It not be used yet, use the openapi instead. + // agentConfig["connectors"] = connector.AIConnectors } // OpenAPI Settings