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.
This commit is contained in:
Max 2025-11-24 18:34:00 +08:00
parent b1d19a2d79
commit bc3dd567b5
7 changed files with 282 additions and 38 deletions

View file

@ -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

View file

@ -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)

View file

@ -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")
}
}

View file

@ -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

View file

@ -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)
}

View file

@ -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.

View file

@ -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