From 0fdd1e0c2a6e2895c13eda0f38afe5ecc96fe39d Mon Sep 17 00:00:00 2001 From: "Jakub A. W" Date: Sat, 6 Dec 2025 23:12:01 +0100 Subject: [PATCH 1/3] chore: linter added + failover placeholder --- .golangci.yml | 11 ++++++++ Makefile | 10 ++++++- README.md | 44 +++++++++++++++++++++++++++++ cmd/gomodel/main.go | 1 + config/config.go | 3 +- config/failover.yaml | 2 ++ internal/core/interfaces.go | 1 + internal/core/types.go | 10 +++---- internal/providers/openai/openai.go | 18 ++++++++---- internal/server/handlers.go | 10 +++++-- internal/server/handlers_test.go | 4 +-- internal/server/http.go | 1 - 12 files changed, 98 insertions(+), 17 deletions(-) create mode 100644 .golangci.yml create mode 100644 config/failover.yaml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..e43999ec --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,11 @@ +run: + timeout: 5m + +linters: + enable: + - errcheck + - gosimple + - govet + - ineffassign + - staticcheck + - unused diff --git a/Makefile b/Makefile index 2af316c5..39c3b21e 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build run clean tidy test +.PHONY: build run clean tidy test lint lint-fix # Build the application build: @@ -20,3 +20,11 @@ tidy: test: go test ./... -v +# Run linter +lint: + $(shell go env GOPATH)/bin/golangci-lint run ./... + +# Run linter with auto-fix +lint-fix: + $(shell go env GOPATH)/bin/golangci-lint run --fix ./... + diff --git a/README.md b/README.md index d20c2c72..dce9959a 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,50 @@ A high-performance LLM gateway written in Go. -d '{"model": "gpt-4o-mini", "messages": [{"role": "user", "content": "Hello!"}]}' ``` +## Development + +### Linting + +This project uses [golangci-lint](https://golangci-lint.run/) for code quality checks. + +#### Installation + +**macOS:** + +```bash +brew install golangci-lint +``` + +**Linux:** + +```bash +curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin +``` + +**Windows:** + +```bash +go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest +``` + +For other installation methods, see the [official documentation](https://golangci-lint.run/welcome/install/). + +#### Usage + +Run the linter: + +```bash +make lint +``` + +Run the linter with auto-fix: + +```bash +make lint-fix +``` + +The linter configuration is defined in `.golangci.yml` and includes essential checks for code quality and correctness. + ## Running with Docker You can use the official `golang:1.21-alpine` image to run the project in a container: diff --git a/cmd/gomodel/main.go b/cmd/gomodel/main.go index b6adb84c..fc6470c6 100644 --- a/cmd/gomodel/main.go +++ b/cmd/gomodel/main.go @@ -1,3 +1,4 @@ +// Package main is the entry point for the LLM gateway server. package main import ( diff --git a/config/config.go b/config/config.go index 0b51569a..2527d3d7 100644 --- a/config/config.go +++ b/config/config.go @@ -1,3 +1,4 @@ +// Package config provides configuration management for the application. package config import ( @@ -33,7 +34,7 @@ func Load() (*Config, error) { viper.SetDefault("server.port", "8088") // Read config file (optional, won't fail if not found) - _ = viper.ReadInConfig() + _ = viper.ReadInConfig() //nolint:errcheck // Environment variables override config file viper.AutomaticEnv() diff --git a/config/failover.yaml b/config/failover.yaml new file mode 100644 index 00000000..0b4aaba3 --- /dev/null +++ b/config/failover.yaml @@ -0,0 +1,2 @@ +#failover: +# "gpt-5": [gemini3-pro, gemini2.5-pro] diff --git a/internal/core/interfaces.go b/internal/core/interfaces.go index a8df0ed5..79784036 100644 --- a/internal/core/interfaces.go +++ b/internal/core/interfaces.go @@ -1,3 +1,4 @@ +// Package core defines the core interfaces and types for the LLM gateway. package core import ( diff --git a/internal/core/types.go b/internal/core/types.go index 55a8ab60..d522bc4d 100644 --- a/internal/core/types.go +++ b/internal/core/types.go @@ -2,10 +2,10 @@ package core // ChatRequest represents the incoming chat completion request type ChatRequest struct { - Model string `json:"model"` - Messages []Message `json:"messages"` Temperature *float64 `json:"temperature,omitempty"` MaxTokens *int `json:"max_tokens,omitempty"` + Model string `json:"model"` + Messages []Message `json:"messages"` Stream bool `json:"stream,omitempty"` } @@ -19,17 +19,17 @@ type Message struct { type ChatResponse struct { ID string `json:"id"` Object string `json:"object"` - Created int64 `json:"created"` Model string `json:"model"` Choices []Choice `json:"choices"` Usage Usage `json:"usage"` + Created int64 `json:"created"` } // Choice represents a single completion choice type Choice struct { - Index int `json:"index"` Message Message `json:"message"` FinishReason string `json:"finish_reason"` + Index int `json:"index"` } // Usage represents token usage information @@ -43,8 +43,8 @@ type Usage struct { type Model struct { ID string `json:"id"` Object string `json:"object"` - Created int64 `json:"created"` OwnedBy string `json:"owned_by"` + Created int64 `json:"created"` } // ModelsResponse represents the response from the /v1/models endpoint diff --git a/internal/providers/openai/openai.go b/internal/providers/openai/openai.go index ac914e4a..4cf01620 100644 --- a/internal/providers/openai/openai.go +++ b/internal/providers/openai/openai.go @@ -1,3 +1,4 @@ +// Package openai provides OpenAI API integration for the LLM gateway. package openai import ( @@ -18,9 +19,9 @@ const ( // Provider implements the core.Provider interface for OpenAI type Provider struct { + httpClient *http.Client apiKey string baseURL string - httpClient *http.Client } // New creates a new OpenAI provider @@ -61,7 +62,9 @@ func (p *Provider) ChatCompletion(ctx context.Context, req *core.ChatRequest) (* if err != nil { return nil, fmt.Errorf("failed to send request: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() //nolint:errcheck + }() respBody, err := io.ReadAll(resp.Body) if err != nil { @@ -103,8 +106,11 @@ func (p *Provider) StreamChatCompletion(ctx context.Context, req *core.ChatReque } if resp.StatusCode != http.StatusOK { - respBody, _ := io.ReadAll(resp.Body) - resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + respBody = []byte("failed to read error response") + } + _ = resp.Body.Close() //nolint:errcheck return nil, fmt.Errorf("OpenAI API error (status %d): %s", resp.StatusCode, string(respBody)) } @@ -124,7 +130,9 @@ func (p *Provider) ListModels(ctx context.Context) (*core.ModelsResponse, error) if err != nil { return nil, fmt.Errorf("failed to send request: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() //nolint:errcheck + }() respBody, err := io.ReadAll(resp.Body) if err != nil { diff --git a/internal/server/handlers.go b/internal/server/handlers.go index 512577c9..3cfa61f3 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -1,3 +1,4 @@ +// Package server provides HTTP handlers and server setup for the LLM gateway. package server import ( @@ -42,14 +43,19 @@ func (h *Handler) ChatCompletion(c echo.Context) error { if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) } - defer stream.Close() + defer func() { + _ = stream.Close() //nolint:errcheck + }() c.Response().Header().Set("Content-Type", "text/event-stream") c.Response().Header().Set("Cache-Control", "no-cache") c.Response().Header().Set("Connection", "keep-alive") c.Response().WriteHeader(http.StatusOK) - io.Copy(c.Response().Writer, stream) + if _, err := io.Copy(c.Response().Writer, stream); err != nil { + // Can't return error after headers are sent, log it + return nil + } return nil } diff --git a/internal/server/handlers_test.go b/internal/server/handlers_test.go index 1dfb4d33..ed02c01a 100644 --- a/internal/server/handlers_test.go +++ b/internal/server/handlers_test.go @@ -15,11 +15,11 @@ import ( // mockProvider implements core.Provider for testing type mockProvider struct { - supportedModels []string + err error response *core.ChatResponse modelsResponse *core.ModelsResponse streamData string - err error + supportedModels []string } func (m *mockProvider) Supports(model string) bool { diff --git a/internal/server/http.go b/internal/server/http.go index d1feed42..64fae04c 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -39,4 +39,3 @@ func New(provider core.Provider) *Server { func (s *Server) Start(addr string) error { return s.echo.Start(addr) } - From 62f82be15afe1e79d8fbbccba041b004c34b09cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20A=2E=20W=C4=85sek?= Date: Sat, 6 Dec 2025 23:17:31 +0100 Subject: [PATCH 2/3] Update config/failover.yaml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- config/failover.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/failover.yaml b/config/failover.yaml index 0b4aaba3..23ee9d11 100644 --- a/config/failover.yaml +++ b/config/failover.yaml @@ -1,2 +1,2 @@ #failover: -# "gpt-5": [gemini3-pro, gemini2.5-pro] +# "gpt-5": [gemini-1.5-pro, gemini2.5-pro] From 64af9f95095787a5e494143b58815bf4a529d088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20A=2E=20W=C4=85sek?= Date: Sat, 6 Dec 2025 23:19:04 +0100 Subject: [PATCH 3/3] Update Makefile Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 39c3b21e..a0162c18 100644 --- a/Makefile +++ b/Makefile @@ -22,9 +22,9 @@ test: # Run linter lint: - $(shell go env GOPATH)/bin/golangci-lint run ./... + golangci-lint run ./... # Run linter with auto-fix lint-fix: - $(shell go env GOPATH)/bin/golangci-lint run --fix ./... + golangci-lint run --fix ./...