fix(web-search): normalize default tenant web search config at runtime

This commit is contained in:
sqkstwj
2026-04-30 10:00:42 +08:00
committed by lyingbug
parent 3b23713c54
commit a5f80b747c
6 changed files with 114 additions and 28 deletions
+12 -13
View File
@@ -152,20 +152,19 @@ func (t *WebSearchTool) Execute(ctx context.Context, args json.RawMessage) (*typ
}
// Get tenant info from context (same approach as search.go)
tenant := ctx.Value(types.TenantInfoContextKey).(*types.Tenant)
if tenant == nil || tenant.WebSearchConfig == nil {
logger.Errorf(ctx, "[Tool][WebSearch] Web search not configured for tenant %d", tenantID)
return &types.ToolResult{
Success: false,
Error: "web search is not configured for this tenant",
}, fmt.Errorf("web search is not configured for tenant %d", tenantID)
var tenant *types.Tenant
if tenantValue := ctx.Value(types.TenantInfoContextKey); tenantValue != nil {
tenant, _ = tenantValue.(*types.Tenant)
}
// Resolve provider ID: tool-level (set from agent config, which already resolved default)
resolvedProviderID := t.providerID
// Create a copy of web search config with maxResults from agent config
searchConfig := *tenant.WebSearchConfig
// Create a copy of the effective web search config with maxResults from agent config.
searchConfig := types.EffectiveWebSearchConfig(nil)
if tenant != nil {
searchConfig = types.EffectiveWebSearchConfig(tenant.WebSearchConfig)
}
searchConfig.MaxResults = t.maxResults
// Perform web search
@@ -175,7 +174,7 @@ func (t *WebSearchTool) Execute(ctx context.Context, args json.RawMessage) (*typ
resolvedProviderID,
searchConfig.MaxResults,
)
webResults, err := t.webSearchService.Search(ctx, resolvedProviderID, &searchConfig, query)
webResults, err := t.webSearchService.Search(ctx, resolvedProviderID, searchConfig, query)
if err != nil {
logger.Errorf(ctx, "[Tool][WebSearch] Web search failed: %v", err)
return &types.ToolResult{
@@ -187,8 +186,8 @@ func (t *WebSearchTool) Execute(ctx context.Context, args json.RawMessage) (*typ
logger.Infof(ctx, "[Tool][WebSearch] Web search returned %d results", len(webResults))
// Apply RAG compression if configured
if len(webResults) > 0 && tenant.WebSearchConfig.CompressionMethod != "none" &&
tenant.WebSearchConfig.CompressionMethod != "" {
if len(webResults) > 0 && searchConfig.CompressionMethod != "none" &&
searchConfig.CompressionMethod != "" {
// Load session-scoped temp KB state from Redis using WebSearchStateRepository
tempKBID, seen, ids := t.webSearchStateService.GetWebSearchTempKBState(ctx, t.sessionID)
@@ -197,7 +196,7 @@ func (t *WebSearchTool) Execute(ctx context.Context, args json.RawMessage) (*typ
logger.Infof(ctx, "[Tool][WebSearch] Applying RAG compression")
compressed, kbID, newSeen, newIDs, err := t.webSearchService.CompressWithRAG(
ctx, t.sessionID, tempKBID, questions, webResults, tenant.WebSearchConfig,
ctx, t.sessionID, tempKBID, questions, webResults, searchConfig,
t.knowledgeBaseService, t.knowledgeService, seen, ids,
)
if err != nil {
@@ -624,22 +624,18 @@ func (p *PluginSearch) searchWebIfEnabled(ctx context.Context, chatManage *types
tenant, _ := types.TenantInfoFromContext(ctx)
providerID := chatManage.WebSearchProviderID
var webConfig *types.WebSearchConfig
if tenant != nil && tenant.WebSearchConfig != nil {
// Clone tenant config so we can safely override MaxResults
cfg := *tenant.WebSearchConfig
webConfig = &cfg
} else if providerID != "" {
webConfig = &types.WebSearchConfig{
MaxResults: 10,
}
} else {
if providerID == "" {
pipelineWarn(ctx, "Search", "web_config_missing", map[string]interface{}{
"tenant_id": chatManage.TenantID,
})
return nil
}
webConfig := types.EffectiveWebSearchConfig(nil)
if tenant != nil {
webConfig = types.EffectiveWebSearchConfig(tenant.WebSearchConfig)
}
// Apply agent-level web search overrides
if chatManage.WebSearchMaxResults > 0 {
webConfig.MaxResults = chatManage.WebSearchMaxResults
@@ -962,8 +962,8 @@ func (s *sessionService) resolveWebSearchMaxResults(ctx context.Context, req *ty
return req.CustomAgent.Config.WebSearchMaxResults
}
tenantInfo, _ := types.TenantInfoFromContext(ctx)
if tenantInfo != nil && tenantInfo.WebSearchConfig != nil && tenantInfo.WebSearchConfig.MaxResults > 0 {
return tenantInfo.WebSearchConfig.MaxResults
if tenantInfo != nil {
return types.EffectiveWebSearchConfig(tenantInfo.WebSearchConfig).MaxResults
}
return 10
return types.DefaultWebSearchMaxResults
}
+4 -2
View File
@@ -750,6 +750,8 @@ func (h *TenantHandler) updateTenantWebSearchConfigInternal(c *gin.Context) {
return
}
cfg = *types.EffectiveWebSearchConfig(&cfg)
// Validate configuration
if cfg.MaxResults < 1 || cfg.MaxResults > 50 {
c.Error(errors.NewBadRequestError("max_results must be between 1 and 50"))
@@ -777,7 +779,7 @@ func (h *TenantHandler) updateTenantWebSearchConfigInternal(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": updatedTenant.WebSearchConfig,
"data": types.EffectiveWebSearchConfig(updatedTenant.WebSearchConfig),
"message": "Web search configuration updated successfully",
})
}
@@ -807,7 +809,7 @@ func (h *TenantHandler) GetTenantWebSearchConfig(c *gin.Context) {
logger.Infof(ctx, "Tenant web search config retrieved successfully, Tenant ID: %d", tenant.ID)
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": tenant.WebSearchConfig,
"data": types.EffectiveWebSearchConfig(tenant.WebSearchConfig),
})
}
+35
View File
@@ -25,6 +25,41 @@ type WebSearchConfig struct {
ProxyURL string `json:"proxy_url,omitempty"` // Optional per-request proxy override; normally empty — use WebSearchProviderEntity.Parameters.proxy_url. Merged at call time when set.
}
const (
DefaultWebSearchMaxResults = 10
DefaultWebSearchCompressionMethod = "none"
)
// DefaultWebSearchConfig returns the shared default tenant-level web search configuration.
func DefaultWebSearchConfig() *WebSearchConfig {
return &WebSearchConfig{
MaxResults: DefaultWebSearchMaxResults,
IncludeDate: false,
CompressionMethod: DefaultWebSearchCompressionMethod,
Blacklist: []string{},
}
}
// EffectiveWebSearchConfig normalizes a possibly empty config to the effective runtime config.
func EffectiveWebSearchConfig(cfg *WebSearchConfig) *WebSearchConfig {
if cfg == nil {
return DefaultWebSearchConfig()
}
normalized := *cfg
if normalized.MaxResults <= 0 {
normalized.MaxResults = DefaultWebSearchMaxResults
}
if normalized.CompressionMethod == "" {
normalized.CompressionMethod = DefaultWebSearchCompressionMethod
}
if normalized.Blacklist == nil {
normalized.Blacklist = []string{}
}
return &normalized
}
// Value implements driver.Valuer interface for WebSearchConfig
func (c WebSearchConfig) Value() (driver.Value, error) {
return json.Marshal(c)
+54
View File
@@ -0,0 +1,54 @@
package types
import "testing"
func TestEffectiveWebSearchConfigUsesDefaultsForNilConfig(t *testing.T) {
cfg := EffectiveWebSearchConfig(nil)
if cfg.MaxResults != DefaultWebSearchMaxResults {
t.Fatalf("MaxResults = %d, want %d", cfg.MaxResults, DefaultWebSearchMaxResults)
}
if cfg.CompressionMethod != DefaultWebSearchCompressionMethod {
t.Fatalf("CompressionMethod = %q, want %q", cfg.CompressionMethod, DefaultWebSearchCompressionMethod)
}
if cfg.Blacklist == nil {
t.Fatal("Blacklist = nil, want empty slice")
}
if len(cfg.Blacklist) != 0 {
t.Fatalf("Blacklist length = %d, want 0", len(cfg.Blacklist))
}
}
func TestEffectiveWebSearchConfigNormalizesZeroValuesWithoutMutatingSource(t *testing.T) {
source := &WebSearchConfig{
IncludeDate: true,
}
cfg := EffectiveWebSearchConfig(source)
if cfg == source {
t.Fatal("EffectiveWebSearchConfig returned original pointer, want normalized copy")
}
if cfg.MaxResults != DefaultWebSearchMaxResults {
t.Fatalf("MaxResults = %d, want %d", cfg.MaxResults, DefaultWebSearchMaxResults)
}
if cfg.CompressionMethod != DefaultWebSearchCompressionMethod {
t.Fatalf("CompressionMethod = %q, want %q", cfg.CompressionMethod, DefaultWebSearchCompressionMethod)
}
if cfg.Blacklist == nil {
t.Fatal("Blacklist = nil, want empty slice")
}
if !cfg.IncludeDate {
t.Fatal("IncludeDate = false, want true")
}
if source.MaxResults != 0 {
t.Fatalf("source MaxResults mutated to %d, want 0", source.MaxResults)
}
if source.CompressionMethod != "" {
t.Fatalf("source CompressionMethod mutated to %q, want empty string", source.CompressionMethod)
}
if source.Blacklist != nil {
t.Fatalf("source Blacklist mutated to non-nil value: %#v", source.Blacklist)
}
}