diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 069bf330d..24aca5216 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -48,6 +48,7 @@ jobs: pkg-config npm install -g markdownlint-cli pip install --user yamllint codespell + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.5.0 - name: Cache Rust dependencies uses: actions/cache@v4 diff --git a/.github/workflows/test-and-build.yml b/.github/workflows/test-and-build.yml index dae1721ea..864c31593 100644 --- a/.github/workflows/test-and-build.yml +++ b/.github/workflows/test-and-build.yml @@ -32,6 +32,7 @@ jobs: make \ build-essential \ pkg-config + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.5.0 - name: Cache Rust dependencies uses: actions/cache@v4 @@ -74,7 +75,6 @@ jobs: run: | pip install -U "huggingface_hub[cli]" hf_transfer - - name: Download models (minimal on PRs) env: CI_MINIMAL_MODELS: ${{ github.event_name == 'pull_request' }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a73c8fed5..8367a1244 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,6 +22,15 @@ repos: language: system files: \.go$ +- repo: local + hooks: + - id: golang-lint + name: go lint + entry: make go-lint + language: system + files: \.go$ + pass_filenames: false + # Markdown specific hooks - repo: local hooks: diff --git a/Dockerfile.precommit b/Dockerfile.precommit index dbd787c48..e4cc844ce 100644 --- a/Dockerfile.precommit +++ b/Dockerfile.precommit @@ -29,3 +29,6 @@ RUN pip install --break-system-packages yamllint # CodeSpell RUN pip install --break-system-packages codespell + +# Golangci-lint +RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.5.0 diff --git a/src/semantic-router/cmd/main.go b/src/semantic-router/cmd/main.go index e41dbfcf7..55a94916b 100644 --- a/src/semantic-router/cmd/main.go +++ b/src/semantic-router/cmd/main.go @@ -11,6 +11,7 @@ import ( "time" "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/vllm-project/semantic-router/src/semantic-router/pkg/api" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/config" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/extproc" @@ -63,16 +64,16 @@ func main() { ServiceVersion: cfg.Observability.Tracing.Resource.ServiceVersion, DeploymentEnvironment: cfg.Observability.Tracing.Resource.DeploymentEnvironment, } - if err := observability.InitTracing(ctx, tracingCfg); err != nil { - observability.Warnf("Failed to initialize tracing: %v", err) + if tracingErr := observability.InitTracing(ctx, tracingCfg); tracingErr != nil { + observability.Warnf("Failed to initialize tracing: %v", tracingErr) } // Set up graceful shutdown for tracing defer func() { shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - if err := observability.ShutdownTracing(shutdownCtx); err != nil { - observability.Errorf("Failed to shutdown tracing: %v", err) + if shutdownErr := observability.ShutdownTracing(shutdownCtx); shutdownErr != nil { + observability.Errorf("Failed to shutdown tracing: %v", shutdownErr) } }() } @@ -86,8 +87,8 @@ func main() { observability.Infof("Received shutdown signal, cleaning up...") shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - if err := observability.ShutdownTracing(shutdownCtx); err != nil { - observability.Errorf("Failed to shutdown tracing: %v", err) + if shutdownErr := observability.ShutdownTracing(shutdownCtx); shutdownErr != nil { + observability.Errorf("Failed to shutdown tracing: %v", shutdownErr) } os.Exit(0) }() @@ -97,8 +98,8 @@ func main() { http.Handle("/metrics", promhttp.Handler()) metricsAddr := fmt.Sprintf(":%d", *metricsPort) observability.Infof("Starting metrics server on %s", metricsAddr) - if err := http.ListenAndServe(metricsAddr, nil); err != nil { - observability.Errorf("Metrics server error: %v", err) + if metricsErr := http.ListenAndServe(metricsAddr, nil); metricsErr != nil { + observability.Errorf("Metrics server error: %v", metricsErr) } }() diff --git a/src/semantic-router/go.mod b/src/semantic-router/go.mod index f96c81095..d5f8a9c04 100644 --- a/src/semantic-router/go.mod +++ b/src/semantic-router/go.mod @@ -32,6 +32,7 @@ require ( google.golang.org/grpc v1.75.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/apimachinery v0.31.4 + sigs.k8s.io/yaml v1.6.0 ) require ( @@ -89,6 +90,7 @@ require ( go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/net v0.43.0 // indirect golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.35.0 // indirect diff --git a/src/semantic-router/go.sum b/src/semantic-router/go.sum index e5e53e8a7..d062bf925 100644 --- a/src/semantic-router/go.sum +++ b/src/semantic-router/go.sum @@ -354,6 +354,10 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -527,5 +531,5 @@ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMm sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/src/semantic-router/pkg/api/server.go b/src/semantic-router/pkg/api/server.go index b41429b61..6f543e1e8 100644 --- a/src/semantic-router/pkg/api/server.go +++ b/src/semantic-router/pkg/api/server.go @@ -225,10 +225,10 @@ func (s *ClassificationAPIServer) setupRoutes() *http.ServeMux { } // handleHealth handles health check requests -func (s *ClassificationAPIServer) handleHealth(w http.ResponseWriter, r *http.Request) { +func (s *ClassificationAPIServer) handleHealth(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"status": "healthy", "service": "classification-api"}`)) + _, _ = w.Write([]byte(`{"status": "healthy", "service": "classification-api"}`)) } // APIOverviewResponse represents the response for GET /api/v1 @@ -363,7 +363,7 @@ type OpenAPIComponents struct { } // handleAPIOverview handles GET /api/v1 for API discovery -func (s *ClassificationAPIServer) handleAPIOverview(w http.ResponseWriter, r *http.Request) { +func (s *ClassificationAPIServer) handleAPIOverview(w http.ResponseWriter, _ *http.Request) { // Build endpoints list from registry, filtering out disabled endpoints endpoints := make([]EndpointInfo, 0, len(endpointRegistry)) for _, metadata := range endpointRegistry { @@ -371,11 +371,7 @@ func (s *ClassificationAPIServer) handleAPIOverview(w http.ResponseWriter, r *ht if !s.enableSystemPromptAPI && (metadata.Path == "/config/system-prompts") { continue } - endpoints = append(endpoints, EndpointInfo{ - Path: metadata.Path, - Method: metadata.Method, - Description: metadata.Description, - }) + endpoints = append(endpoints, EndpointInfo(metadata)) } response := APIOverviewResponse{ @@ -497,13 +493,13 @@ func (s *ClassificationAPIServer) generateOpenAPISpec() OpenAPISpec { } // handleOpenAPISpec serves the OpenAPI 3.0 specification at /openapi.json -func (s *ClassificationAPIServer) handleOpenAPISpec(w http.ResponseWriter, r *http.Request) { +func (s *ClassificationAPIServer) handleOpenAPISpec(w http.ResponseWriter, _ *http.Request) { spec := s.generateOpenAPISpec() s.writeJSONResponse(w, http.StatusOK, spec) } // handleSwaggerUI serves the Swagger UI at /docs -func (s *ClassificationAPIServer) handleSwaggerUI(w http.ResponseWriter, r *http.Request) { +func (s *ClassificationAPIServer) handleSwaggerUI(w http.ResponseWriter, _ *http.Request) { // Serve a simple HTML page that loads Swagger UI from CDN html := ` @@ -545,7 +541,7 @@ func (s *ClassificationAPIServer) handleSwaggerUI(w http.ResponseWriter, r *http w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(http.StatusOK) - w.Write([]byte(html)) + _, _ = w.Write([]byte(html)) } // handleIntentClassification handles intent classification requests @@ -609,7 +605,7 @@ func (s *ClassificationAPIServer) handleSecurityDetection(w http.ResponseWriter, } // Placeholder handlers for remaining endpoints -func (s *ClassificationAPIServer) handleCombinedClassification(w http.ResponseWriter, r *http.Request) { +func (s *ClassificationAPIServer) handleCombinedClassification(w http.ResponseWriter, _ *http.Request) { s.writeErrorResponse(w, http.StatusNotImplemented, "NOT_IMPLEMENTED", "Combined classification not implemented yet") } @@ -631,7 +627,7 @@ func (s *ClassificationAPIServer) handleBatchClassification(w http.ResponseWrite // Check if texts field exists in JSON var rawReq map[string]interface{} - if err := json.Unmarshal(body, &rawReq); err != nil { + if unmarshalErr := json.Unmarshal(body, &rawReq); unmarshalErr != nil { metrics.RecordBatchClassificationError("unified", "invalid_json") s.writeErrorResponse(w, http.StatusBadRequest, "INVALID_INPUT", "Invalid JSON format") return @@ -645,9 +641,9 @@ func (s *ClassificationAPIServer) handleBatchClassification(w http.ResponseWrite } var req BatchClassificationRequest - if err := s.parseJSONRequest(r, &req); err != nil { + if parseErr := s.parseJSONRequest(r, &req); parseErr != nil { metrics.RecordBatchClassificationError("unified", "parse_request_failed") - s.writeErrorResponse(w, http.StatusBadRequest, "INVALID_INPUT", err.Error()) + s.writeErrorResponse(w, http.StatusBadRequest, "INVALID_INPUT", parseErr.Error()) return } @@ -660,9 +656,9 @@ func (s *ClassificationAPIServer) handleBatchClassification(w http.ResponseWrite } // Validate task_type if provided - if err := validateTaskType(req.TaskType); err != nil { + if validateErr := validateTaskType(req.TaskType); validateErr != nil { metrics.RecordBatchClassificationError("unified", "invalid_task_type") - s.writeErrorResponse(w, http.StatusBadRequest, "INVALID_TASK_TYPE", err.Error()) + s.writeErrorResponse(w, http.StatusBadRequest, "INVALID_TASK_TYPE", validateErr.Error()) return } @@ -703,12 +699,12 @@ func (s *ClassificationAPIServer) handleBatchClassification(w http.ResponseWrite s.writeJSONResponse(w, http.StatusOK, response) } -func (s *ClassificationAPIServer) handleModelsInfo(w http.ResponseWriter, r *http.Request) { +func (s *ClassificationAPIServer) handleModelsInfo(w http.ResponseWriter, _ *http.Request) { response := s.buildModelsInfoResponse() s.writeJSONResponse(w, http.StatusOK, response) } -func (s *ClassificationAPIServer) handleClassifierInfo(w http.ResponseWriter, r *http.Request) { +func (s *ClassificationAPIServer) handleClassifierInfo(w http.ResponseWriter, _ *http.Request) { if s.config == nil { s.writeJSONResponse(w, http.StatusOK, map[string]interface{}{ "status": "no_config", @@ -726,7 +722,7 @@ func (s *ClassificationAPIServer) handleClassifierInfo(w http.ResponseWriter, r // handleOpenAIModels handles OpenAI-compatible model listing at /v1/models // It returns all models discoverable from the router configuration plus a synthetic "auto" model. -func (s *ClassificationAPIServer) handleOpenAIModels(w http.ResponseWriter, r *http.Request) { +func (s *ClassificationAPIServer) handleOpenAIModels(w http.ResponseWriter, _ *http.Request) { now := time.Now().Unix() // Start with the special "auto" model always available from the router @@ -763,15 +759,15 @@ func (s *ClassificationAPIServer) handleOpenAIModels(w http.ResponseWriter, r *h s.writeJSONResponse(w, http.StatusOK, resp) } -func (s *ClassificationAPIServer) handleClassificationMetrics(w http.ResponseWriter, r *http.Request) { +func (s *ClassificationAPIServer) handleClassificationMetrics(w http.ResponseWriter, _ *http.Request) { s.writeErrorResponse(w, http.StatusNotImplemented, "NOT_IMPLEMENTED", "Classification metrics not implemented yet") } -func (s *ClassificationAPIServer) handleGetConfig(w http.ResponseWriter, r *http.Request) { +func (s *ClassificationAPIServer) handleGetConfig(w http.ResponseWriter, _ *http.Request) { s.writeErrorResponse(w, http.StatusNotImplemented, "NOT_IMPLEMENTED", "Get config not implemented yet") } -func (s *ClassificationAPIServer) handleUpdateConfig(w http.ResponseWriter, r *http.Request) { +func (s *ClassificationAPIServer) handleUpdateConfig(w http.ResponseWriter, _ *http.Request) { s.writeErrorResponse(w, http.StatusNotImplemented, "NOT_IMPLEMENTED", "Update config not implemented yet") } @@ -1096,7 +1092,7 @@ type SystemPromptUpdateRequest struct { } // handleGetSystemPrompts handles GET /config/system-prompts -func (s *ClassificationAPIServer) handleGetSystemPrompts(w http.ResponseWriter, r *http.Request) { +func (s *ClassificationAPIServer) handleGetSystemPrompts(w http.ResponseWriter, _ *http.Request) { cfg := s.config if cfg == nil { http.Error(w, "Configuration not available", http.StatusInternalServerError) diff --git a/src/semantic-router/pkg/cache/cache_test.go b/src/semantic-router/pkg/cache/cache_test.go index d99d2450d..11c1efa9f 100644 --- a/src/semantic-router/pkg/cache/cache_test.go +++ b/src/semantic-router/pkg/cache/cache_test.go @@ -7,14 +7,13 @@ import ( "testing" "time" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/prometheus/client_golang/prometheus/testutil" + candle_binding "github.com/vllm-project/semantic-router/candle-binding" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/cache" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/metrics" - - "github.com/prometheus/client_golang/prometheus/testutil" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" ) func TestCache(t *testing.T) { @@ -29,9 +28,7 @@ var _ = BeforeSuite(func() { }) var _ = Describe("Cache Package", func() { - var ( - tempDir string - ) + var tempDir string BeforeEach(func() { var err error @@ -213,7 +210,6 @@ development: Expect(backend).To(BeNil()) }) }) - }) Describe("ValidateCacheConfig", func() { @@ -412,9 +408,7 @@ development: }) Describe("InMemoryCache", func() { - var ( - inMemoryCache cache.CacheBackend - ) + var inMemoryCache cache.CacheBackend BeforeEach(func() { options := cache.InMemoryCacheOptions{ @@ -435,7 +429,7 @@ development: It("should implement CacheBackend interface", func() { // Check that the concrete type implements the interface - var _ cache.CacheBackend = inMemoryCache + _ = inMemoryCache Expect(inMemoryCache).NotTo(BeNil()) }) diff --git a/src/semantic-router/pkg/cache/inmemory_cache_integration_test.go b/src/semantic-router/pkg/cache/inmemory_cache_integration_test.go index caffe6b92..c970aedf3 100644 --- a/src/semantic-router/pkg/cache/inmemory_cache_integration_test.go +++ b/src/semantic-router/pkg/cache/inmemory_cache_integration_test.go @@ -46,9 +46,9 @@ func TestInMemoryCacheIntegration(t *testing.T) { // Step 3: Access first entry multiple times to increase its frequency for range 2 { - responseBody, found, err := cache.FindSimilar("test-model", "Hello world") - if err != nil { - t.Logf("FindSimilar failed (expected due to high threshold): %v", err) + responseBody, found, findErr := cache.FindSimilar("test-model", "Hello world") + if findErr != nil { + t.Logf("FindSimilar failed (expected due to high threshold): %v", findErr) } if !found { t.Errorf("Expected to find similar entry for first query") diff --git a/src/semantic-router/pkg/cache/milvus_cache.go b/src/semantic-router/pkg/cache/milvus_cache.go index 6e5d6af7d..4af891841 100644 --- a/src/semantic-router/pkg/cache/milvus_cache.go +++ b/src/semantic-router/pkg/cache/milvus_cache.go @@ -11,10 +11,11 @@ import ( "github.com/milvus-io/milvus-sdk-go/v2/client" "github.com/milvus-io/milvus-sdk-go/v2/entity" + "sigs.k8s.io/yaml" + candle_binding "github.com/vllm-project/semantic-router/candle-binding" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/metrics" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/observability" - "gopkg.in/yaml.v3" ) // MilvusConfig defines the complete configuration structure for Milvus cache backend @@ -168,7 +169,7 @@ func NewMilvusCache(options MilvusCacheOptions) (*MilvusCache, error) { // loadMilvusConfig reads and parses the Milvus configuration from file func loadMilvusConfig(configPath string) (*MilvusConfig, error) { if configPath == "" { - return nil, fmt.Errorf("Milvus config path is required") + return nil, fmt.Errorf("milvus config path is required") } data, err := os.ReadFile(configPath) @@ -303,8 +304,8 @@ func (c *MilvusCache) createCollection() error { } // Create collection - if err := c.client.CreateCollection(ctx, schema, 1); err != nil { - return err + if createErr := c.client.CreateCollection(ctx, schema, 1); createErr != nil { + return createErr } // Create index with updated API @@ -364,7 +365,6 @@ func (c *MilvusCache) UpdateWithResponse(requestID string, responseBody []byte) results, err := c.client.Query(ctx, c.collectionName, []string{}, queryExpr, []string{"id", "model", "query", "request_body"}) - if err != nil { observability.Debugf("MilvusCache.UpdateWithResponse: query failed: %v", err) metrics.RecordCacheOperation("milvus", "update_response", "error", time.Since(start).Seconds()) @@ -528,7 +528,6 @@ func (c *MilvusCache) FindSimilar(model string, query string) ([]byte, bool, err c.config.Search.TopK, searchParam, ) - if err != nil { observability.Debugf("MilvusCache.FindSimilar: search failed: %v", err) atomic.AddInt64(&c.missCount, 1) @@ -622,7 +621,7 @@ func (c *MilvusCache) GetStats() CacheStats { if err == nil { // Extract entity count from statistics if entityCount, ok := stats["row_count"]; ok { - fmt.Sscanf(entityCount, "%d", &totalEntries) + _, _ = fmt.Sscanf(entityCount, "%d", &totalEntries) observability.Debugf("MilvusCache.GetStats: collection '%s' contains %d entries", c.collectionName, totalEntries) } diff --git a/src/semantic-router/pkg/config/config_test.go b/src/semantic-router/pkg/config/config_test.go index b40284762..e48491422 100644 --- a/src/semantic-router/pkg/config/config_test.go +++ b/src/semantic-router/pkg/config/config_test.go @@ -6,12 +6,11 @@ import ( "sync" "testing" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" "gopkg.in/yaml.v3" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/config" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" ) func TestConfig(t *testing.T) { diff --git a/src/semantic-router/pkg/config/mmlu_categories_test.go b/src/semantic-router/pkg/config/mmlu_categories_test.go index fa2685f20..27172bfcd 100644 --- a/src/semantic-router/pkg/config/mmlu_categories_test.go +++ b/src/semantic-router/pkg/config/mmlu_categories_test.go @@ -3,7 +3,6 @@ package config_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "gopkg.in/yaml.v3" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/config" diff --git a/src/semantic-router/pkg/connectivity/mcp/http_client.go b/src/semantic-router/pkg/connectivity/mcp/http_client.go index 432df5441..69d649126 100644 --- a/src/semantic-router/pkg/connectivity/mcp/http_client.go +++ b/src/semantic-router/pkg/connectivity/mcp/http_client.go @@ -308,7 +308,7 @@ func (c *HTTPClient) Ping(ctx context.Context) error { } // RefreshCapabilities reloads tools, resources, and prompts -func (c *HTTPClient) RefreshCapabilities(ctx context.Context) error { +func (c *HTTPClient) RefreshCapabilities(_ context.Context) error { if !c.connected { return fmt.Errorf("client not connected") } diff --git a/src/semantic-router/pkg/connectivity/mcp/interface.go b/src/semantic-router/pkg/connectivity/mcp/interface.go index 112a797cb..968f67ad3 100644 --- a/src/semantic-router/pkg/connectivity/mcp/interface.go +++ b/src/semantic-router/pkg/connectivity/mcp/interface.go @@ -51,7 +51,7 @@ func NewBaseClient(name string, config ClientConfig) *BaseClient { name: name, config: config, connected: false, - logHandler: func(level LoggingLevel, message string) { + logHandler: func(_ LoggingLevel, message string) { // Default log handler - can be overridden }, } diff --git a/src/semantic-router/pkg/connectivity/mcp/stdio_client.go b/src/semantic-router/pkg/connectivity/mcp/stdio_client.go index 0f38ad381..fcfae34f3 100644 --- a/src/semantic-router/pkg/connectivity/mcp/stdio_client.go +++ b/src/semantic-router/pkg/connectivity/mcp/stdio_client.go @@ -30,7 +30,7 @@ func (c *StdioClient) Connect() error { return nil } - c.log(LoggingLevelInfo, fmt.Sprintf("Connecting to MCP server with stdio transport")) + c.log(LoggingLevelInfo, "Connecting to MCP server with stdio transport") if c.config.Command == "" { return fmt.Errorf("command is required for stdio transport") @@ -75,10 +75,10 @@ func (c *StdioClient) Connect() error { } }() - c.log(LoggingLevelDebug, fmt.Sprintf("Starting MCP client...")) + c.log(LoggingLevelDebug, "Starting MCP client...") // Note: Connection starts automatically; Start() method is not required - c.log(LoggingLevelDebug, fmt.Sprintf("Initializing MCP client...")) + c.log(LoggingLevelDebug, "Initializing MCP client...") // Initialize the client with a simple request (no params needed for basic initialization) initResult, err := c.mcpClient.Initialize(ctx, mcp.InitializeRequest{}) if err != nil { @@ -225,7 +225,6 @@ func (c *StdioClient) CallTool(ctx context.Context, name string, arguments map[s callReq.Params.Arguments = arguments result, err := c.mcpClient.CallTool(ctx, callReq) - if err != nil { c.log(LoggingLevelError, fmt.Sprintf("Tool call failed for %s: %v", name, err)) c.log(LoggingLevelError, fmt.Sprintf("Arguments were: %+v", arguments)) @@ -257,7 +256,6 @@ func (c *StdioClient) ReadResource(ctx context.Context, uri string) (*mcp.ReadRe readReq.Params.URI = uri result, err := c.mcpClient.ReadResource(ctx, readReq) - if err != nil { c.log(LoggingLevelError, fmt.Sprintf("Resource read failed: %v", err)) return nil, fmt.Errorf("resource read failed: %w", err) @@ -290,7 +288,6 @@ func (c *StdioClient) GetPrompt(ctx context.Context, name string, arguments map[ getPromptReq.Params.Arguments = stringArgs result, err := c.mcpClient.GetPrompt(ctx, getPromptReq) - if err != nil { c.log(LoggingLevelError, fmt.Sprintf("Prompt get failed: %v", err)) return nil, fmt.Errorf("prompt get failed: %w", err) diff --git a/src/semantic-router/pkg/extproc/caching_test.go b/src/semantic-router/pkg/extproc/caching_test.go index b67831af7..fcd4dc797 100644 --- a/src/semantic-router/pkg/extproc/caching_test.go +++ b/src/semantic-router/pkg/extproc/caching_test.go @@ -4,12 +4,11 @@ import ( "encoding/json" "time" + ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/openai/openai-go" - ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" - "github.com/vllm-project/semantic-router/src/semantic-router/pkg/cache" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/config" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/extproc" diff --git a/src/semantic-router/pkg/extproc/edge_cases_test.go b/src/semantic-router/pkg/extproc/edge_cases_test.go index f402edacd..03c3b0fdc 100644 --- a/src/semantic-router/pkg/extproc/edge_cases_test.go +++ b/src/semantic-router/pkg/extproc/edge_cases_test.go @@ -6,11 +6,10 @@ import ( "strings" "time" + ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" - "github.com/vllm-project/semantic-router/src/semantic-router/pkg/cache" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/config" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/extproc" diff --git a/src/semantic-router/pkg/extproc/endpoint_selection_test.go b/src/semantic-router/pkg/extproc/endpoint_selection_test.go index 480056c53..045b39c84 100644 --- a/src/semantic-router/pkg/extproc/endpoint_selection_test.go +++ b/src/semantic-router/pkg/extproc/endpoint_selection_test.go @@ -3,11 +3,11 @@ package extproc_test import ( "encoding/json" + core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" - ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/config" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/extproc" ) @@ -284,7 +284,7 @@ var _ = Describe("Endpoint Selection", func() { // Exactly one should be set, not both and not neither Expect(hasValue || hasRawValue).To(BeTrue(), "Header %s should have either Value or RawValue set", header.Key) - Expect(!(hasValue && hasRawValue)).To(BeTrue(), "Header %s should not have both Value and RawValue set (causes Envoy 500 error)", header.Key) + Expect(!hasValue || !hasRawValue).To(BeTrue(), "Header %s should not have both Value and RawValue set (causes Envoy 500 error)", header.Key) } } }) diff --git a/src/semantic-router/pkg/extproc/extproc_test.go b/src/semantic-router/pkg/extproc/extproc_test.go index cc4a8d90d..80033c4ae 100644 --- a/src/semantic-router/pkg/extproc/extproc_test.go +++ b/src/semantic-router/pkg/extproc/extproc_test.go @@ -3,10 +3,9 @@ package extproc_test import ( "testing" + ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - - ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" ) func TestExtProc(t *testing.T) { diff --git a/src/semantic-router/pkg/extproc/metrics_integration_test.go b/src/semantic-router/pkg/extproc/metrics_integration_test.go index 0604022de..c7f1e5ebf 100644 --- a/src/semantic-router/pkg/extproc/metrics_integration_test.go +++ b/src/semantic-router/pkg/extproc/metrics_integration_test.go @@ -4,14 +4,14 @@ import ( "encoding/json" "time" + core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/openai/openai-go" - - core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" - ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" + "github.com/vllm-project/semantic-router/src/semantic-router/pkg/cache" ) diff --git a/src/semantic-router/pkg/extproc/models_endpoint_test.go b/src/semantic-router/pkg/extproc/models_endpoint_test.go index 9fbd5d171..369c2b01b 100644 --- a/src/semantic-router/pkg/extproc/models_endpoint_test.go +++ b/src/semantic-router/pkg/extproc/models_endpoint_test.go @@ -7,6 +7,7 @@ import ( core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" typev3 "github.com/envoyproxy/go-control-plane/envoy/type/v3" + "github.com/vllm-project/semantic-router/src/semantic-router/pkg/config" ) diff --git a/src/semantic-router/pkg/extproc/processor.go b/src/semantic-router/pkg/extproc/processor.go index 9b299ba2b..d550c97f5 100644 --- a/src/semantic-router/pkg/extproc/processor.go +++ b/src/semantic-router/pkg/extproc/processor.go @@ -6,10 +6,11 @@ import ( "io" ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" - "github.com/vllm-project/semantic-router/src/semantic-router/pkg/metrics" - "github.com/vllm-project/semantic-router/src/semantic-router/pkg/observability" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + + "github.com/vllm-project/semantic-router/src/semantic-router/pkg/metrics" + "github.com/vllm-project/semantic-router/src/semantic-router/pkg/observability" ) // Process implements the ext_proc calls @@ -25,7 +26,7 @@ func (r *OpenAIRouter) Process(stream ext_proc.ExternalProcessor_ProcessServer) req, err := stream.Recv() if err != nil { // Handle EOF - this indicates the client has closed the stream gracefully - if err == io.EOF { + if errors.Is(err, io.EOF) { observability.Infof("Stream ended gracefully") return nil } diff --git a/src/semantic-router/pkg/extproc/reason_mode_config_test.go b/src/semantic-router/pkg/extproc/reason_mode_config_test.go index e37c62343..b4da22d27 100644 --- a/src/semantic-router/pkg/extproc/reason_mode_config_test.go +++ b/src/semantic-router/pkg/extproc/reason_mode_config_test.go @@ -10,7 +10,7 @@ import ( ) // TestReasoningModeConfiguration demonstrates how the reasoning mode works with the new config-based approach -func TestReasoningModeConfiguration(t *testing.T) { +func TestReasoningModeConfiguration(_ *testing.T) { fmt.Println("=== Configuration-Based Reasoning Mode Test ===") // Create a mock configuration for testing @@ -228,7 +228,7 @@ requestBody := buildRequestBody(model, messages, useReasoning, stream) } // TestAddReasoningModeToRequestBody tests the addReasoningModeToRequestBody function -func TestAddReasoningModeToRequestBody(t *testing.T) { +func TestAddReasoningModeToRequestBody(_ *testing.T) { fmt.Println("=== Testing addReasoningModeToRequestBody Function ===") // Create a mock router with family-based reasoning config @@ -294,8 +294,8 @@ func TestAddReasoningModeToRequestBody(t *testing.T) { // Verify the modification var modifiedRequest map[string]interface{} - if err := json.Unmarshal(modifiedBody, &modifiedRequest); err != nil { - fmt.Printf("Error unmarshaling modified request: %v\n", err) + if unmarshalErr := json.Unmarshal(modifiedBody, &modifiedRequest); unmarshalErr != nil { + fmt.Printf("Error unmarshaling modified request: %v\n", unmarshalErr) return } @@ -341,8 +341,8 @@ func TestAddReasoningModeToRequestBody(t *testing.T) { fmt.Printf("Modified deepseek request with reasoning:\n%s\n\n", string(modifiedDeepseekBody)) var modifiedDeepseekRequest map[string]interface{} - if err := json.Unmarshal(modifiedDeepseekBody, &modifiedDeepseekRequest); err != nil { - fmt.Printf("Error unmarshaling modified deepseek request: %v\n", err) + if unmarshalErr := json.Unmarshal(modifiedDeepseekBody, &modifiedDeepseekRequest); unmarshalErr != nil { + fmt.Printf("Error unmarshaling modified deepseek request: %v\n", unmarshalErr) return } diff --git a/src/semantic-router/pkg/extproc/reason_mode_selector.go b/src/semantic-router/pkg/extproc/reason_mode_selector.go index 10f5d8245..55536ad2f 100644 --- a/src/semantic-router/pkg/extproc/reason_mode_selector.go +++ b/src/semantic-router/pkg/extproc/reason_mode_selector.go @@ -62,7 +62,6 @@ func (r *OpenAIRouter) getReasoningModeAndCategory(query string) (bool, string) func (r *OpenAIRouter) getEntropyBasedReasoningModeAndCategory(query string) (bool, string, entropy.ReasoningDecision) { // Use the classifier with entropy analysis categoryName, confidence, reasoningDecision, err := r.Classifier.ClassifyCategoryWithEntropy(query) - if err != nil { observability.Warnf("Entropy-based classification error: %v, falling back to traditional method", err) @@ -158,7 +157,7 @@ func (r *OpenAIRouter) setReasoningModeToRequestBody(requestBody []byte, enabled delete(requestMap, "chat_template_kwargs") delete(requestMap, "reasoning_effort") - var appliedEffort string = "" + appliedEffort := "" var reasoningApplied bool diff --git a/src/semantic-router/pkg/extproc/reason_mode_selector_test.go b/src/semantic-router/pkg/extproc/reason_mode_selector_test.go index 0a8a1f0a9..06fa527c1 100644 --- a/src/semantic-router/pkg/extproc/reason_mode_selector_test.go +++ b/src/semantic-router/pkg/extproc/reason_mode_selector_test.go @@ -329,13 +329,14 @@ func TestSetReasoningModeToRequestBody(t *testing.T) { } // Validate the specific parameter based on model type - if tc.model == "deepseek-v31" || tc.model == "ds-1.5b" { + switch tc.model { + case "deepseek-v31", "ds-1.5b": if thinkingValue, exists := kwargs["thinking"]; !exists { t.Fatalf("Expected 'thinking' parameter in chat_template_kwargs for DeepSeek model") } else if thinkingValue != true { t.Fatalf("Expected 'thinking' to be true, got %v", thinkingValue) } - } else if tc.model == "qwen3-7b" { + case "qwen3-7b": if thinkingValue, exists := kwargs["enable_thinking"]; !exists { t.Fatalf("Expected 'enable_thinking' parameter in chat_template_kwargs for Qwen3 model") } else if thinkingValue != true { diff --git a/src/semantic-router/pkg/extproc/reasoning_integration_test.go b/src/semantic-router/pkg/extproc/reasoning_integration_test.go index 14445ce30..44b88c82a 100644 --- a/src/semantic-router/pkg/extproc/reasoning_integration_test.go +++ b/src/semantic-router/pkg/extproc/reasoning_integration_test.go @@ -118,8 +118,8 @@ func TestReasoningModeIntegration(t *testing.T) { } var modifiedRequest map[string]interface{} - if err := json.Unmarshal(modifiedBody, &modifiedRequest); err != nil { - t.Fatalf("Failed to unmarshal modified request: %v", err) + if unmarshalErr := json.Unmarshal(modifiedBody, &modifiedRequest); unmarshalErr != nil { + t.Fatalf("Failed to unmarshal modified request: %v", unmarshalErr) } // Check if chat_template_kwargs was added for DeepSeek model @@ -187,7 +187,7 @@ func TestReasoningModeIntegration(t *testing.T) { // Test case 4: Test buildReasoningRequestFields function with config-driven approach t.Run("buildReasoningRequestFields returns correct values", func(t *testing.T) { // Create a router with sample configurations for testing - router := &OpenAIRouter{ + testRouter := &OpenAIRouter{ Config: &config.RouterConfig{ DefaultReasoningEffort: "medium", ReasoningFamilies: map[string]config.ReasoningFamilyConfig{ @@ -215,7 +215,7 @@ func TestReasoningModeIntegration(t *testing.T) { } // Test with DeepSeek model and reasoning enabled - fields, _ := router.buildReasoningRequestFields("deepseek-v31", true, "test-category") + fields, _ := testRouter.buildReasoningRequestFields("deepseek-v31", true, "test-category") if fields == nil { t.Error("Expected non-nil fields for DeepSeek model with reasoning enabled") } @@ -228,13 +228,13 @@ func TestReasoningModeIntegration(t *testing.T) { } // Test with DeepSeek model and reasoning disabled - fields, _ = router.buildReasoningRequestFields("deepseek-v31", false, "test-category") + fields, _ = testRouter.buildReasoningRequestFields("deepseek-v31", false, "test-category") if fields != nil { t.Errorf("Expected nil fields for DeepSeek model with reasoning disabled, got %v", fields) } // Test with Qwen3 model and reasoning enabled - fields, _ = router.buildReasoningRequestFields("qwen3-model", true, "test-category") + fields, _ = testRouter.buildReasoningRequestFields("qwen3-model", true, "test-category") if fields == nil { t.Error("Expected non-nil fields for Qwen3 model with reasoning enabled") } @@ -247,7 +247,7 @@ func TestReasoningModeIntegration(t *testing.T) { } // Test with unknown model (should return no fields) - fields, effort := router.buildReasoningRequestFields("unknown-model", true, "test-category") + fields, effort := testRouter.buildReasoningRequestFields("unknown-model", true, "test-category") if fields != nil { t.Errorf("Expected nil fields for unknown model with reasoning enabled, got %v", fields) } diff --git a/src/semantic-router/pkg/extproc/request_handler.go b/src/semantic-router/pkg/extproc/request_handler.go index bdd10a45f..9f261f207 100644 --- a/src/semantic-router/pkg/extproc/request_handler.go +++ b/src/semantic-router/pkg/extproc/request_handler.go @@ -33,11 +33,6 @@ func parseOpenAIRequest(data []byte) (*openai.ChatCompletionNewParams, error) { return &req, nil } -// serializeOpenAIRequest converts request back to JSON -func serializeOpenAIRequest(req *openai.ChatCompletionNewParams) ([]byte, error) { - return json.Marshal(req) -} - // extractStreamParam extracts the stream parameter from the original request body func extractStreamParam(originalBody []byte) bool { var requestMap map[string]interface{} @@ -380,7 +375,7 @@ func (r *OpenAIRouter) handleRequestBody(v *ext_proc.ProcessingRequest_RequestBo } // Store the original model - originalModel := string(openAIRequest.Model) + originalModel := openAIRequest.Model observability.Infof("Original model: %s", originalModel) // Set model on span @@ -501,7 +496,7 @@ func (r *OpenAIRouter) handleCaching(ctx *RequestContext) (*ext_proc.ProcessingR startTime := time.Now() // Try to find a similar cached response - cachedResponse, found, err := r.Cache.FindSimilar(requestModel, requestQuery) + cachedResponse, found, cacheErr := r.Cache.FindSimilar(requestModel, requestQuery) lookupTime := time.Since(startTime).Milliseconds() observability.SetSpanAttributes(span, @@ -509,9 +504,9 @@ func (r *OpenAIRouter) handleCaching(ctx *RequestContext) (*ext_proc.ProcessingR attribute.Bool(observability.AttrCacheHit, found), attribute.Int64(observability.AttrCacheLookupTimeMs, lookupTime)) - if err != nil { - observability.Errorf("Error searching cache: %v", err) - observability.RecordError(span, err) + if cacheErr != nil { + observability.Errorf("Error searching cache: %v", cacheErr) + observability.RecordError(span, cacheErr) } else if found { // Mark this request as a cache hit ctx.VSRCacheHit = true @@ -727,7 +722,7 @@ func (r *OpenAIRouter) handleModelRouting(openAIRequest *openai.ChatCompletionNe ctx.TraceContext = backendCtx // Modify the model in the request - openAIRequest.Model = openai.ChatModel(matchedModel) + openAIRequest.Model = matchedModel // Serialize the modified request with stream parameter preserved modifiedBody, err := serializeOpenAIRequestWithStream(openAIRequest, ctx.ExpectStreamingResponse) @@ -1146,7 +1141,7 @@ type OpenAIModelList struct { } // handleModelsRequest handles GET /v1/models requests and returns a direct response -func (r *OpenAIRouter) handleModelsRequest(path string) (*ext_proc.ProcessingResponse, error) { +func (r *OpenAIRouter) handleModelsRequest(_ string) (*ext_proc.ProcessingResponse, error) { now := time.Now().Unix() // Start with the special "auto" model always available from the router diff --git a/src/semantic-router/pkg/extproc/request_processing_test.go b/src/semantic-router/pkg/extproc/request_processing_test.go index 7f0fea6be..89bbacd43 100644 --- a/src/semantic-router/pkg/extproc/request_processing_test.go +++ b/src/semantic-router/pkg/extproc/request_processing_test.go @@ -4,13 +4,12 @@ import ( "encoding/json" "time" + core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/openai/openai-go" - core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" - ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" - "github.com/vllm-project/semantic-router/src/semantic-router/pkg/cache" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/config" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/extproc" @@ -361,7 +360,6 @@ var _ = Describe("Request Processing", func() { Describe("handleResponseBody", func() { It("should process response body with token parsing", func() { - openAIResponse := openai.ChatCompletion{ ID: "chatcmpl-123", Object: "chat.completion", diff --git a/src/semantic-router/pkg/extproc/response_handler.go b/src/semantic-router/pkg/extproc/response_handler.go index ce22b281b..ab5fc4fe1 100644 --- a/src/semantic-router/pkg/extproc/response_handler.go +++ b/src/semantic-router/pkg/extproc/response_handler.go @@ -9,8 +9,8 @@ import ( core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" http_ext "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_proc/v3" ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" - "github.com/openai/openai-go" + "github.com/vllm-project/semantic-router/src/semantic-router/pkg/headers" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/metrics" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/observability" diff --git a/src/semantic-router/pkg/extproc/router.go b/src/semantic-router/pkg/extproc/router.go index 90eed7c57..dd40c7e4e 100644 --- a/src/semantic-router/pkg/extproc/router.go +++ b/src/semantic-router/pkg/extproc/router.go @@ -6,7 +6,6 @@ import ( ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" candle_binding "github.com/vllm-project/semantic-router/candle-binding" - "github.com/vllm-project/semantic-router/src/semantic-router/pkg/cache" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/config" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/observability" @@ -70,8 +69,8 @@ func NewOpenAIRouter(configPath string) (*OpenAIRouter, error) { } // Initialize the BERT model for similarity search - if err := candle_binding.InitModel(cfg.BertModel.ModelID, cfg.BertModel.UseCPU); err != nil { - return nil, fmt.Errorf("failed to initialize BERT model: %w", err) + if initErr := candle_binding.InitModel(cfg.BertModel.ModelID, cfg.BertModel.UseCPU); initErr != nil { + return nil, fmt.Errorf("failed to initialize BERT model: %w", initErr) } categoryDescriptions := cfg.GetCategoryDescriptions() @@ -121,8 +120,8 @@ func NewOpenAIRouter(configPath string) (*OpenAIRouter, error) { // Load tools from file if enabled and path is provided if toolsDatabase.IsEnabled() && cfg.Tools.ToolsDBPath != "" { - if err := toolsDatabase.LoadToolsFromFile(cfg.Tools.ToolsDBPath); err != nil { - observability.Warnf("Failed to load tools from file %s: %v", cfg.Tools.ToolsDBPath, err) + if loadErr := toolsDatabase.LoadToolsFromFile(cfg.Tools.ToolsDBPath); loadErr != nil { + observability.Warnf("Failed to load tools from file %s: %v", cfg.Tools.ToolsDBPath, loadErr) } observability.Infof("Tools database enabled with threshold: %.4f, top-k: %d", toolsThreshold, cfg.Tools.TopK) diff --git a/src/semantic-router/pkg/extproc/security_test.go b/src/semantic-router/pkg/extproc/security_test.go index 58edc2091..b5a5f1212 100644 --- a/src/semantic-router/pkg/extproc/security_test.go +++ b/src/semantic-router/pkg/extproc/security_test.go @@ -7,16 +7,14 @@ import ( "sync" "time" + ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" - "github.com/vllm-project/semantic-router/src/semantic-router/pkg/cache" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/config" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/extproc" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/utils/classification" - "github.com/vllm-project/semantic-router/src/semantic-router/pkg/utils/pii" ) diff --git a/src/semantic-router/pkg/extproc/server.go b/src/semantic-router/pkg/extproc/server.go index 84fa3f5b8..30de1f07d 100644 --- a/src/semantic-router/pkg/extproc/server.go +++ b/src/semantic-router/pkg/extproc/server.go @@ -3,6 +3,7 @@ package extproc import ( "context" "crypto/tls" + "errors" "fmt" "net" "os" @@ -14,10 +15,11 @@ import ( ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" "github.com/fsnotify/fsnotify" - "github.com/vllm-project/semantic-router/src/semantic-router/pkg/observability" - tlsutil "github.com/vllm-project/semantic-router/src/semantic-router/pkg/utils/tls" "google.golang.org/grpc" "google.golang.org/grpc/credentials" + + "github.com/vllm-project/semantic-router/src/semantic-router/pkg/observability" + tlsutil "github.com/vllm-project/semantic-router/src/semantic-router/pkg/utils/tls" ) // Server represents a gRPC server for the Envoy ExtProc @@ -94,7 +96,7 @@ func (s *Server) Start() error { // Run the server in a separate goroutine serverErrCh := make(chan error, 1) go func() { - if err := s.server.Serve(lis); err != nil && err != grpc.ErrServerStopped { + if err := s.server.Serve(lis); err != nil && !errors.Is(err, grpc.ErrServerStopped) { observability.Errorf("Server error: %v", err) serverErrCh <- err } else { diff --git a/src/semantic-router/pkg/extproc/stream_handling_test.go b/src/semantic-router/pkg/extproc/stream_handling_test.go index b9c2e0c3c..b3344fc54 100644 --- a/src/semantic-router/pkg/extproc/stream_handling_test.go +++ b/src/semantic-router/pkg/extproc/stream_handling_test.go @@ -5,11 +5,10 @@ import ( "fmt" "strings" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" diff --git a/src/semantic-router/pkg/extproc/test_utils_test.go b/src/semantic-router/pkg/extproc/test_utils_test.go index 3492a4021..56ebad9e5 100644 --- a/src/semantic-router/pkg/extproc/test_utils_test.go +++ b/src/semantic-router/pkg/extproc/test_utils_test.go @@ -227,8 +227,8 @@ func CreateTestRouter(cfg *config.RouterConfig) (*extproc.OpenAIRouter, error) { } // Initialize the BERT model for similarity search - if err := candle_binding.InitModel(cfg.BertModel.ModelID, cfg.BertModel.UseCPU); err != nil { - return nil, fmt.Errorf("failed to initialize BERT model: %w", err) + if initErr := candle_binding.InitModel(cfg.BertModel.ModelID, cfg.BertModel.UseCPU); initErr != nil { + return nil, fmt.Errorf("failed to initialize BERT model: %w", initErr) } // Create semantic cache diff --git a/src/semantic-router/pkg/extproc/utils.go b/src/semantic-router/pkg/extproc/utils.go index fd183288a..ca46e36dd 100644 --- a/src/semantic-router/pkg/extproc/utils.go +++ b/src/semantic-router/pkg/extproc/utils.go @@ -2,6 +2,7 @@ package extproc import ( ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" + "github.com/vllm-project/semantic-router/src/semantic-router/pkg/observability" ) diff --git a/src/semantic-router/pkg/metrics/metrics_test.go b/src/semantic-router/pkg/metrics/metrics_test.go index 371ad51d8..34e96d3f4 100644 --- a/src/semantic-router/pkg/metrics/metrics_test.go +++ b/src/semantic-router/pkg/metrics/metrics_test.go @@ -72,7 +72,7 @@ func TestBatchClassificationMetrics(t *testing.T) { } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + t.Run(tt.name, func(_ *testing.T) { // Record metrics RecordBatchClassificationRequest(tt.processingType) RecordBatchSizeDistribution(tt.processingType, tt.batchSize) @@ -112,7 +112,7 @@ func TestGetBatchSizeRange(t *testing.T) { } // TestConcurrentGoroutineTracking tests goroutine tracking functionality -func TestConcurrentGoroutineTracking(t *testing.T) { +func TestConcurrentGoroutineTracking(_ *testing.T) { batchID := "test_batch_123" // Simulate goroutine start @@ -141,7 +141,7 @@ func BenchmarkBatchClassificationMetrics(b *testing.B) { } // TestMetricsIntegration tests the integration of all batch classification metrics -func TestMetricsIntegration(t *testing.T) { +func TestMetricsIntegration(_ *testing.T) { // Simulate a complete batch processing scenario processingType := "concurrent" batchSize := 8 diff --git a/src/semantic-router/pkg/observability/logging.go b/src/semantic-router/pkg/observability/logging.go index 9f3581920..333abfe49 100644 --- a/src/semantic-router/pkg/observability/logging.go +++ b/src/semantic-router/pkg/observability/logging.go @@ -112,6 +112,7 @@ func getenvDefault(k, d string) string { } return v } + func parseBool(s string) bool { s = strings.TrimSpace(strings.ToLower(s)) return s == "1" || s == "true" || s == "yes" || s == "on" diff --git a/src/semantic-router/pkg/services/classification.go b/src/semantic-router/pkg/services/classification.go index c0a52a09d..1ce598b06 100644 --- a/src/semantic-router/pkg/services/classification.go +++ b/src/semantic-router/pkg/services/classification.go @@ -406,7 +406,7 @@ func (s *ClassificationService) CheckSecurity(req SecurityRequest) (*SecurityRes } // Helper methods -func (s *ClassificationService) getRecommendedModel(category string, confidence float64) string { +func (s *ClassificationService) getRecommendedModel(category string, _ float64) string { // TODO: Implement model recommendation logic based on category return fmt.Sprintf("%s-specialized-model", category) } @@ -438,7 +438,7 @@ func (s *ClassificationService) ClassifyBatchUnified(texts []string) (*UnifiedBa } // ClassifyBatchUnifiedWithOptions performs unified batch classification with options support -func (s *ClassificationService) ClassifyBatchUnifiedWithOptions(texts []string, options interface{}) (*UnifiedBatchResponse, error) { +func (s *ClassificationService) ClassifyBatchUnifiedWithOptions(texts []string, _ interface{}) (*UnifiedBatchResponse, error) { if len(texts) == 0 { return nil, fmt.Errorf("texts cannot be empty") } diff --git a/src/semantic-router/pkg/tools/tools.go b/src/semantic-router/pkg/tools/tools.go index e3279f9e8..5367c271d 100644 --- a/src/semantic-router/pkg/tools/tools.go +++ b/src/semantic-router/pkg/tools/tools.go @@ -8,6 +8,7 @@ import ( "sync" "github.com/openai/openai-go" + candle_binding "github.com/vllm-project/semantic-router/candle-binding" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/observability" ) diff --git a/src/semantic-router/pkg/tools/tools_test.go b/src/semantic-router/pkg/tools/tools_test.go index 7771dc4c8..fca7f8388 100644 --- a/src/semantic-router/pkg/tools/tools_test.go +++ b/src/semantic-router/pkg/tools/tools_test.go @@ -6,13 +6,13 @@ import ( "path/filepath" "testing" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" "github.com/openai/openai-go" "github.com/openai/openai-go/packages/param" + candle_binding "github.com/vllm-project/semantic-router/candle-binding" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/tools" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" ) func TestTools(t *testing.T) { diff --git a/src/semantic-router/pkg/utils/classification/classifier.go b/src/semantic-router/pkg/utils/classification/classifier.go index be3efeebb..82660e159 100644 --- a/src/semantic-router/pkg/utils/classification/classifier.go +++ b/src/semantic-router/pkg/utils/classification/classifier.go @@ -729,7 +729,7 @@ func (c *Classifier) DetectPIIInContent(allContent []string) []string { for _, content := range allContent { if content != "" { - //TODO: classifier may not handle the entire content, so we need to split the content into smaller chunks + // TODO: classifier may not handle the entire content, so we need to split the content into smaller chunks piiTypes, err := c.ClassifyPII(content) if err != nil { observability.Errorf("PII classification error: %v", err) diff --git a/src/semantic-router/pkg/utils/classification/classifier_test.go b/src/semantic-router/pkg/utils/classification/classifier_test.go index 5606b3a14..9296375b1 100644 --- a/src/semantic-router/pkg/utils/classification/classifier_test.go +++ b/src/semantic-router/pkg/utils/classification/classifier_test.go @@ -23,17 +23,17 @@ type MockCategoryInference struct { classifyWithProbsError error } -func (m *MockCategoryInference) Classify(text string) (candle_binding.ClassResult, error) { +func (m *MockCategoryInference) Classify(_ string) (candle_binding.ClassResult, error) { return m.classifyResult, m.classifyError } -func (m *MockCategoryInference) ClassifyWithProbabilities(text string) (candle_binding.ClassResultWithProbs, error) { +func (m *MockCategoryInference) ClassifyWithProbabilities(_ string) (candle_binding.ClassResultWithProbs, error) { return m.classifyWithProbsResult, m.classifyWithProbsError } type MockCategoryInitializer struct{ InitError error } -func (m *MockCategoryInitializer) Init(modelID string, useCPU bool, numClasses ...int) error { +func (m *MockCategoryInitializer) Init(_ string, useCPU bool, numClasses ...int) error { return m.InitError } @@ -390,7 +390,6 @@ var _ = Describe("category classification and model selection", func() { ) Describe("select best model internal", func() { - It("should select best model without filter", func() { cat := &config.Category{ Name: "test", @@ -489,7 +488,7 @@ type MockJailbreakInitializer struct { InitError error } -func (m *MockJailbreakInitializer) Init(modelID string, useCPU bool, numClasses ...int) error { +func (m *MockJailbreakInitializer) Init(_ string, useCPU bool, numClasses ...int) error { return m.InitError } @@ -697,7 +696,7 @@ var _ = Describe("jailbreak detection", func() { type MockPIIInitializer struct{ InitError error } -func (m *MockPIIInitializer) Init(modelID string, useCPU bool) error { return m.InitError } +func (m *MockPIIInitializer) Init(_ string, useCPU bool) error { return m.InitError } type MockPIIInferenceResponse struct { classifyTokensResult candle_binding.TokenClassificationResult @@ -718,7 +717,7 @@ func (m *MockPIIInference) setMockResponse(text string, entities []candle_bindin } } -func (m *MockPIIInference) ClassifyTokens(text string, configPath string) (candle_binding.TokenClassificationResult, error) { +func (m *MockPIIInference) ClassifyTokens(text string, _ string) (candle_binding.TokenClassificationResult, error) { if response, exists := m.responseMap[text]; exists { return response.classifyTokensResult, response.classifyTokensError } @@ -1034,7 +1033,6 @@ var _ = Describe("get models for category", func() { }) func TestUpdateBestModel(t *testing.T) { - classifier := &Classifier{} bestScore := 0.5 @@ -1052,7 +1050,6 @@ func TestUpdateBestModel(t *testing.T) { } func TestForEachModelScore(t *testing.T) { - c := &Classifier{} cat := &config.Category{ ModelScores: []config.ModelScore{ diff --git a/src/semantic-router/pkg/utils/classification/mcp_classifier.go b/src/semantic-router/pkg/utils/classification/mcp_classifier.go index a9587db13..18ebda33d 100644 --- a/src/semantic-router/pkg/utils/classification/mcp_classifier.go +++ b/src/semantic-router/pkg/utils/classification/mcp_classifier.go @@ -8,6 +8,7 @@ import ( "time" "github.com/mark3labs/mcp-go/mcp" + candle_binding "github.com/vllm-project/semantic-router/candle-binding" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/config" mcpclient "github.com/vllm-project/semantic-router/src/semantic-router/pkg/connectivity/mcp" diff --git a/src/semantic-router/pkg/utils/classification/mcp_classifier_test.go b/src/semantic-router/pkg/utils/classification/mcp_classifier_test.go index 0d57f2f3a..9a6937847 100644 --- a/src/semantic-router/pkg/utils/classification/mcp_classifier_test.go +++ b/src/semantic-router/pkg/utils/classification/mcp_classifier_test.go @@ -4,10 +4,10 @@ import ( "context" "errors" + "github.com/mark3labs/mcp-go/mcp" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/mark3labs/mcp-go/mcp" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/config" mcpclient "github.com/vllm-project/semantic-router/src/semantic-router/pkg/connectivity/mcp" ) diff --git a/src/semantic-router/pkg/utils/classification/model_discovery.go b/src/semantic-router/pkg/utils/classification/model_discovery.go index a27b1611f..d0b5a4caf 100644 --- a/src/semantic-router/pkg/utils/classification/model_discovery.go +++ b/src/semantic-router/pkg/utils/classification/model_discovery.go @@ -145,7 +145,7 @@ func AutoDiscoverModels(modelsDir string) (*ModelPaths, error) { return nil }) if err != nil { - return nil, fmt.Errorf("error scanning models directory: %v", err) + return nil, fmt.Errorf("error scanning models directory: %w", err) } // Intelligent architecture selection based on performance priority: BERT > RoBERTa > ModernBERT @@ -333,12 +333,12 @@ func AutoInitializeUnifiedClassifier(modelsDir string) (*UnifiedClassifier, erro // Discover models paths, err := AutoDiscoverModels(modelsDir) if err != nil { - return nil, fmt.Errorf("model discovery failed: %v", err) + return nil, fmt.Errorf("model discovery failed: %w", err) } // Validate paths if err := ValidateModelPaths(paths); err != nil { - return nil, fmt.Errorf("model validation failed: %v", err) + return nil, fmt.Errorf("model validation failed: %w", err) } // Check if we should use LoRA models @@ -372,7 +372,7 @@ func initializeLoRAUnifiedClassifier(paths *ModelPaths) (*UnifiedClassifier, err // Pre-initialize LoRA C bindings to avoid lazy loading during first API call if err := classifier.initializeLoRABindings(); err != nil { - return nil, fmt.Errorf("failed to pre-initialize LoRA bindings: %v", err) + return nil, fmt.Errorf("failed to pre-initialize LoRA bindings: %w", err) } classifier.loraInitialized = true @@ -385,7 +385,7 @@ func initializeLegacyUnifiedClassifier(paths *ModelPaths) (*UnifiedClassifier, e categoryMappingPath := filepath.Join(paths.IntentClassifier, "category_mapping.json") categoryMapping, err := LoadCategoryMapping(categoryMappingPath) if err != nil { - return nil, fmt.Errorf("failed to load category mapping from %s: %v", categoryMappingPath, err) + return nil, fmt.Errorf("failed to load category mapping from %s: %w", categoryMappingPath, err) } // Extract intent labels in correct order (by index) @@ -401,10 +401,10 @@ func initializeLegacyUnifiedClassifier(paths *ModelPaths) (*UnifiedClassifier, e // Load PII labels from the actual model's mapping file var piiLabels []string piiMappingPath := filepath.Join(paths.PIIClassifier, "pii_type_mapping.json") - if _, err := os.Stat(piiMappingPath); err == nil { - piiMapping, err := LoadPIIMapping(piiMappingPath) - if err != nil { - return nil, fmt.Errorf("failed to load PII mapping from %s: %v", piiMappingPath, err) + if _, statErr := os.Stat(piiMappingPath); statErr == nil { + piiMapping, loadErr := LoadPIIMapping(piiMappingPath) + if loadErr != nil { + return nil, fmt.Errorf("failed to load PII mapping from %s: %w", piiMappingPath, loadErr) } // Extract labels from PII mapping (ordered by index) piiLabels = make([]string, len(piiMapping.IdxToLabel)) @@ -422,10 +422,10 @@ func initializeLegacyUnifiedClassifier(paths *ModelPaths) (*UnifiedClassifier, e // Load security labels from the actual model's mapping file var securityLabels []string securityMappingPath := filepath.Join(paths.SecurityClassifier, "jailbreak_type_mapping.json") - if _, err := os.Stat(securityMappingPath); err == nil { - jailbreakMapping, err := LoadJailbreakMapping(securityMappingPath) - if err != nil { - return nil, fmt.Errorf("failed to load jailbreak mapping from %s: %v", securityMappingPath, err) + if _, statErr2 := os.Stat(securityMappingPath); statErr2 == nil { + jailbreakMapping, loadErr2 := LoadJailbreakMapping(securityMappingPath) + if loadErr2 != nil { + return nil, fmt.Errorf("failed to load jailbreak mapping from %s: %w", securityMappingPath, loadErr2) } // Extract labels from jailbreak mapping (ordered by index) securityLabels = make([]string, len(jailbreakMapping.IdxToLabel)) @@ -455,7 +455,7 @@ func initializeLegacyUnifiedClassifier(paths *ModelPaths) (*UnifiedClassifier, e false, // Default to GPU, will fallback to CPU if needed ) if err != nil { - return nil, fmt.Errorf("unified classifier initialization failed: %v", err) + return nil, fmt.Errorf("unified classifier initialization failed: %w", err) } return classifier, nil diff --git a/src/semantic-router/pkg/utils/classification/model_discovery_test.go b/src/semantic-router/pkg/utils/classification/model_discovery_test.go index 8cd2f9813..555b0e33d 100644 --- a/src/semantic-router/pkg/utils/classification/model_discovery_test.go +++ b/src/semantic-router/pkg/utils/classification/model_discovery_test.go @@ -17,10 +17,10 @@ func TestAutoDiscoverModels(t *testing.T) { securityDir := filepath.Join(tempDir, "jailbreak_classifier_modernbert-base_model") // Create directories - os.MkdirAll(modernbertDir, 0o755) - os.MkdirAll(intentDir, 0o755) - os.MkdirAll(piiDir, 0o755) - os.MkdirAll(securityDir, 0o755) + _ = os.MkdirAll(modernbertDir, 0o755) + _ = os.MkdirAll(intentDir, 0o755) + _ = os.MkdirAll(piiDir, 0o755) + _ = os.MkdirAll(securityDir, 0o755) // Create mock model files createMockModelFile(t, modernbertDir, "config.json") @@ -83,10 +83,10 @@ func TestValidateModelPaths(t *testing.T) { piiDir := filepath.Join(tempDir, "pii") securityDir := filepath.Join(tempDir, "security") - os.MkdirAll(modernbertDir, 0o755) - os.MkdirAll(intentDir, 0o755) - os.MkdirAll(piiDir, 0o755) - os.MkdirAll(securityDir, 0o755) + _ = os.MkdirAll(modernbertDir, 0o755) + _ = os.MkdirAll(intentDir, 0o755) + _ = os.MkdirAll(piiDir, 0o755) + _ = os.MkdirAll(securityDir, 0o755) // Create model files createMockModelFile(t, modernbertDir, "config.json") @@ -151,7 +151,7 @@ func TestGetModelDiscoveryInfo(t *testing.T) { tempDir := t.TempDir() modernbertDir := filepath.Join(tempDir, "modernbert-base") - os.MkdirAll(modernbertDir, 0o755) + _ = os.MkdirAll(modernbertDir, 0o755) createMockModelFile(t, modernbertDir, "config.json") info := GetModelDiscoveryInfo(tempDir) @@ -228,7 +228,7 @@ func createMockModelFile(t *testing.T, dir, filename string) { defer file.Close() // Write some dummy content - file.WriteString(`{"mock": "model file"}`) + _, _ = file.WriteString(`{"mock": "model file"}`) } func TestAutoDiscoverModels_RealModels(t *testing.T) { @@ -326,10 +326,10 @@ func BenchmarkAutoDiscoverModels(b *testing.B) { piiDir := filepath.Join(tempDir, "pii_classifier_modernbert-base_presidio_token_model") securityDir := filepath.Join(tempDir, "jailbreak_classifier_modernbert-base_model") - os.MkdirAll(modernbertDir, 0o755) - os.MkdirAll(intentDir, 0o755) - os.MkdirAll(piiDir, 0o755) - os.MkdirAll(securityDir, 0o755) + _ = os.MkdirAll(modernbertDir, 0o755) + _ = os.MkdirAll(intentDir, 0o755) + _ = os.MkdirAll(piiDir, 0o755) + _ = os.MkdirAll(securityDir, 0o755) // Create mock files using helper createMockModelFileForBench(b, modernbertDir, "config.json") @@ -339,7 +339,7 @@ func BenchmarkAutoDiscoverModels(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - AutoDiscoverModels(tempDir) + _, _ = AutoDiscoverModels(tempDir) } } @@ -351,5 +351,5 @@ func createMockModelFileForBench(b *testing.B, dir, filename string) { b.Fatalf("Failed to create mock file %s: %v", filePath, err) } defer file.Close() - file.WriteString(`{"mock": "model file"}`) + _, _ = file.WriteString(`{"mock": "model file"}`) } diff --git a/src/semantic-router/pkg/utils/classification/unified_classifier.go b/src/semantic-router/pkg/utils/classification/unified_classifier.go index 68d76d783..fb1abded5 100644 --- a/src/semantic-router/pkg/utils/classification/unified_classifier.go +++ b/src/semantic-router/pkg/utils/classification/unified_classifier.go @@ -274,7 +274,7 @@ func (uc *UnifiedClassifier) classifyBatchWithLoRA(texts []string, startTime tim // Lazy initialization of LoRA C bindings if !uc.loraInitialized { if err := uc.initializeLoRABindings(); err != nil { - return nil, fmt.Errorf("failed to initialize loRA bindings: %v", err) + return nil, fmt.Errorf("failed to initialize loRA bindings: %w", err) } uc.loraInitialized = true } @@ -311,7 +311,6 @@ func (uc *UnifiedClassifier) classifyBatchWithLoRA(texts []string, startTime tim // classifyBatchLegacy uses legacy ModernBERT models (lower confidence) func (uc *UnifiedClassifier) classifyBatchLegacy(texts []string, startTime time.Time) (*UnifiedBatchResults, error) { - // Convert Go strings to C string array cTexts := make([]*C.char, len(texts)) for i, text := range texts { diff --git a/src/semantic-router/pkg/utils/classification/unified_classifier_test.go b/src/semantic-router/pkg/utils/classification/unified_classifier_test.go index 0baa039e7..335691dbb 100644 --- a/src/semantic-router/pkg/utils/classification/unified_classifier_test.go +++ b/src/semantic-router/pkg/utils/classification/unified_classifier_test.go @@ -274,8 +274,10 @@ func BenchmarkUnifiedClassifier_SingleVsBatch(b *testing.B) { } // Global classifier instance for integration tests to avoid repeated initialization -var globalTestClassifier *UnifiedClassifier -var globalTestClassifierOnce sync.Once +var ( + globalTestClassifier *UnifiedClassifier + globalTestClassifierOnce sync.Once +) // getTestClassifier returns a shared classifier instance for all integration tests func getTestClassifier(t *testing.T) *UnifiedClassifier { diff --git a/src/semantic-router/pkg/utils/entropy/entropy.go b/src/semantic-router/pkg/utils/entropy/entropy.go index ee1602cb4..2cddb068a 100644 --- a/src/semantic-router/pkg/utils/entropy/entropy.go +++ b/src/semantic-router/pkg/utils/entropy/entropy.go @@ -60,15 +60,16 @@ func AnalyzeEntropy(probabilities []float32) EntropyResult { // Determine uncertainty level var uncertaintyLevel string - if normalizedEntropy >= 0.8 { + switch { + case normalizedEntropy >= 0.8: uncertaintyLevel = "very_high" - } else if normalizedEntropy >= 0.6 { + case normalizedEntropy >= 0.6: uncertaintyLevel = "high" - } else if normalizedEntropy >= 0.4 { + case normalizedEntropy >= 0.4: uncertaintyLevel = "medium" - } else if normalizedEntropy >= 0.2 { + case normalizedEntropy >= 0.2: uncertaintyLevel = "low" - } else { + default: uncertaintyLevel = "very_low" } @@ -96,7 +97,6 @@ func MakeEntropyBasedReasoningDecision( categoryReasoningMap map[string]bool, baseConfidenceThreshold float64, ) ReasoningDecision { - if len(probabilities) == 0 || len(categoryNames) == 0 { return ReasoningDecision{ UseReasoning: false, @@ -214,8 +214,7 @@ func getTopCategories(probabilities []float32, categoryNames []string, topN int) }) // Return top N - n := min(topN, len(pairs)) - return pairs[:n] + return pairs[:int(math.Min(float64(topN), float64(len(pairs))))] } // Helper function to make weighted decision from top categories @@ -249,11 +248,3 @@ func makeWeightedDecision(topCategories []CategoryProbability, categoryReasoning TopCategories: topCategories, } } - -// Helper function to get minimum of two integers -func min(a, b int) int { - if a < b { - return a - } - return b -} diff --git a/src/semantic-router/pkg/utils/entropy/entropy_test.go b/src/semantic-router/pkg/utils/entropy/entropy_test.go index 5c192c781..2d7c2e1b7 100644 --- a/src/semantic-router/pkg/utils/entropy/entropy_test.go +++ b/src/semantic-router/pkg/utils/entropy/entropy_test.go @@ -329,15 +329,16 @@ func TestEntropyMetricsIntegration(t *testing.T) { normalizedEntropy := CalculateNormalizedEntropy(tc.probabilities) var uncertaintyLevel string - if normalizedEntropy >= 0.8 { + switch { + case normalizedEntropy >= 0.8: uncertaintyLevel = "very_high" - } else if normalizedEntropy >= 0.6 { + case normalizedEntropy >= 0.6: uncertaintyLevel = "high" - } else if normalizedEntropy >= 0.4 { + case normalizedEntropy >= 0.4: uncertaintyLevel = "medium" - } else if normalizedEntropy >= 0.2 { + case normalizedEntropy >= 0.2: uncertaintyLevel = "low" - } else { + default: uncertaintyLevel = "very_low" } diff --git a/src/semantic-router/pkg/utils/http/response.go b/src/semantic-router/pkg/utils/http/response.go index d9460d3e9..b7114baa3 100644 --- a/src/semantic-router/pkg/utils/http/response.go +++ b/src/semantic-router/pkg/utils/http/response.go @@ -9,6 +9,7 @@ import ( ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" typev3 "github.com/envoyproxy/go-control-plane/envoy/type/v3" "github.com/openai/openai-go" + "github.com/vllm-project/semantic-router/src/semantic-router/pkg/headers" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/metrics" "github.com/vllm-project/semantic-router/src/semantic-router/pkg/observability" diff --git a/src/semantic-router/pkg/utils/tls/tls.go b/src/semantic-router/pkg/utils/tls/tls.go index 840a80a4c..a6d66618e 100644 --- a/src/semantic-router/pkg/utils/tls/tls.go +++ b/src/semantic-router/pkg/utils/tls/tls.go @@ -17,7 +17,7 @@ func CreateSelfSignedTLSCertificate() (tls.Certificate, error) { serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) if err != nil { - return tls.Certificate{}, fmt.Errorf("error creating serial number: %v", err) + return tls.Certificate{}, fmt.Errorf("error creating serial number: %w", err) } now := time.Now() notBefore := now.UTC() @@ -35,19 +35,19 @@ func CreateSelfSignedTLSCertificate() (tls.Certificate, error) { priv, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { - return tls.Certificate{}, fmt.Errorf("error generating key: %v", err) + return tls.Certificate{}, fmt.Errorf("error generating key: %w", err) } derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) if err != nil { - return tls.Certificate{}, fmt.Errorf("error creating certificate: %v", err) + return tls.Certificate{}, fmt.Errorf("error creating certificate: %w", err) } certBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) privBytes, err := x509.MarshalPKCS8PrivateKey(priv) if err != nil { - return tls.Certificate{}, fmt.Errorf("error marshalling private key: %v", err) + return tls.Certificate{}, fmt.Errorf("error marshalling private key: %w", err) } keyBytes := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}) diff --git a/tools/linter/go/.golangci.yml b/tools/linter/go/.golangci.yml new file mode 100644 index 000000000..852ba3450 --- /dev/null +++ b/tools/linter/go/.golangci.yml @@ -0,0 +1,166 @@ +version: "2" + +run: + timeout: "5m" + +linters: + + # enable specific linters + enable: + - bodyclose # checks that HTTP response body is closed properly to prevent resource leaks + - copyloopvar # detects improper loop variable usage in closures (fixed in Go 1.22+) + - depguard # checks for disallowed package dependencies + - errorlint # finds common mistakes in error handling like wrapping and type assertions + - gocritic # provides extensive code quality checks for performance, style, and diagnostics + - gosec # security checker that inspects code for common security problems + - importas # enforces consistent import aliases for specific packages + - misspell # finds commonly misspelled English words in code + - revive # fast, configurable, extensible Go linter (successor to golint) + - staticcheck # advanced static analysis tool that detects bugs, performance issues, and simplifications + - testifylint # checks for best practices when using the testify testing framework + - unconvert # removes unnecessary type conversions + + settings: + depguard: + rules: + Main: + deny: + - pkg: github.com/gogo/protobuf + desc: gogo/protobuf is deprecated, use golang/protobuf + # - pkg: gopkg.in/yaml.v2 + # desc: use sigs.k8s.io/yaml instead + # - pkg: gopkg.in/yaml.v3 + # desc: use sigs.k8s.io/yaml instead + - pkg: k8s.io/utils/pointer + desc: use k8s.io/utils/ptr instead + # skip some errors from linters + gocritic: + disabled-checks: + - ifElseChain # Allow if-else chains for better readability in some cases + gosec: + excludes: + - G114 # Allow http.ListenAndServe without timeout for internal services + - G306 # Allow 0644 file permissions in test files + - G402 # Allow flexible TLS config for development + - G404 # Allow math/rand for non-cryptographic purposes + - G501 # Allow MD5 for non-security checksums (cache keys) + - G401 # Allow MD5 usage in cache implementation + govet: + disable: + - fieldalignment + enable-all: true + importas: + # go module alias configuration. + alias: + - pkg: sigs.k8s.io/gateway-api/apis/v1 + alias: gwapiv1 + no-unaliased: true + no-extra-aliases: false + revive: + rules: + - name: dot-imports + disabled: true # Allow dot imports in test files (Ginkgo/Gomega style) + - name: exported + arguments: + - "checkPrivateReceivers" + - "disableStutteringCheck" # Allow stuttering names in some contexts + - name: unused-parameter + disabled: true # Allow unused parameters for interface implementations + - name: receiver-naming + disabled: true # Allow flexible receiver naming + - name: empty-block + disabled: true # Allow empty blocks for documentation purposes + - name: indent-error-flow + disabled: true # Allow flexible error flow style + testifylint: + enable: + - bool-compare + - compares + - empty + - error-is-as + - error-nil + - expected-actual + - len + - suite-dont-use-pkg + - suite-extra-assert-call + disable: + - float-compare + - go-require + - require-error # Allow assert.NoError in addition to require.NoError + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + - linters: + - godot + path: api + - linters: + - staticcheck + text: "SA1019:" + - linters: + - staticcheck + text: "SA1012:" # Allow nil context in test files + - linters: + - staticcheck + text: "SA5011" # Allow possible nil pointer dereference in test assertions + path: "_test\\.go$" + - linters: + - bodyclose + path: test/e2e + - linters: + - gosec + text: "G306:" + path: "_test\\.go$" + - linters: + - revive + text: "dot-imports:" + path: "_test\\.go$" + - linters: + - revive + text: "unused-parameter:" + - linters: + - revive + text: "receiver-naming:" + - linters: + - revive + text: "indent-error-flow:" + - linters: + - revive + text: "empty-block:" + - linters: + - revive + text: "exported.*stutters" + - linters: + - testifylint + text: "require-error:" + - linters: + - gocritic + text: "ifElseChain:" + paths: + - examples$ + +issues: + max-issues-per-linter: 0 + max-same-issues: 0 + +formatters: + # enable gofumpt and gci for formatting + enable: + - gci + - gofumpt + settings: + gci: + sections: + - standard + - default + - prefix(github.com/vllm-project/semantic-router) + exclusions: + generated: lax + paths: + - examples$ + - zz_generated + \ No newline at end of file diff --git a/tools/make/build-run-test.mk b/tools/make/build-run-test.mk index b85490d99..64fc68dbc 100644 --- a/tools/make/build-run-test.mk +++ b/tools/make/build-run-test.mk @@ -34,7 +34,7 @@ test-semantic-router: build-router cd src/semantic-router && CGO_ENABLED=1 go test -v ./... # Test the Rust library and the Go binding -test: vet check-go-mod-tidy download-models test-binding test-semantic-router +test: vet go-lint check-go-mod-tidy download-models test-binding test-semantic-router # Clean built artifacts clean: diff --git a/tools/make/golang.mk b/tools/make/golang.mk index 06441e201..0c9c73bd1 100644 --- a/tools/make/golang.mk +++ b/tools/make/golang.mk @@ -2,6 +2,23 @@ # = Everything For Golang = # ======== golang.mk ======== +# Run go lint check for Go modules +# Refer: https://golangci-lint.run/ +# if local run, add -v for verbose output +go-lint: + @$(LOG_TARGET) + @echo "Running golangci-lint for src/semantic-router..." + @cd src/semantic-router/ && golangci-lint run ./... --config ../../tools/linter/go/.golangci.yml + @echo "✅ src/semantic-router go module lint passed" + +# golangci-lint fix for Go modules +# Tips: only fix src/semantic-router and some files may need manual fix. +go-lint-fix: + @$(LOG_TARGET) + @echo "Running golangci-lint fix for src/semantic-router..." + @cd src/semantic-router/ && golangci-lint run ./... --fix --config ../../tools/linter/go/.golangci.yml + @echo "✅ src/semantic-router go module lint fix applied" + # Run go vet for all Go modules vet: @$(LOG_TARGET) @@ -41,4 +58,4 @@ generate-deepcopy: install-controller-gen @cd src/semantic-router && controller-gen object:headerFile=./hack/boilerplate.go.txt paths=./pkg/apis/vllm.ai/v1alpha1 generate-api: generate-deepcopy generate-crd - @echo "Generated all API artifacts" \ No newline at end of file + @echo "Generated all API artifacts"