Kardbord/hfapigo

Announcing v4: Production-grade overhaul, concurrency, and new "Client", "Service", and "Option" patterns

Open

#72 aperta il 11 apr 2026

Vedi su GitHub
 (0 commenti) (1 reazione) (1 assegnatario)Go (9 fork)auto 404
enhancementhelp wanted

Metriche repository

Star
 (63 star)
Metriche merge PR
 (Metriche PR in attesa)

Descrizione

I'm working on a major overhaul of this package, and I wanted to give a heads up about what to expect for the upcoming v4 release and how to get involved if you're interested in contributing.

TL;DR

  • v3 is being deprecated
  • Repo/package is being renamed: hfapigohfgo
  • Individually configurable clients are being added
  • Production grade: Focus on concurrency safety, deterministic behavior, test coverage, dependency injection, and configurability
  • Best-effort adherence to OSSF Best Practices
  • Contribution branch: PRs welcome — please open them against the v4-draft branch. Please leave a comment here or open an issue to avoid duplicated work.

What's Changing in v4

Package Rename

The package is now hfgo (module github.com/Kardbord/hfgo/v4).

OpenSSF Best Practices

From v4 onward, this project will aim to adhere to OpenSSF's Best Practices as closely as possible. The goal of this project is to be safe, reliable, and easy to use.

Production-Focused Redesign: From Package-Level State to Explicit Clients

In v3: There was no client object. Instead, you'd set a package-level API key variable and then call module functions directly:

hfapigo.SetAPIKey("my-key")
resp, err := hfapigo.SendTextGenerationRequest(model, req)

This approach had significant limitations:

  • Not thread-safe: Multiple goroutines sharing the same package-level API key variable required careful synchronization.
  • Single global configuration: All requests used the same API key and settings. Switching credentials meant mutating global state, which is error-prone and impacts all in-flight requests.
  • No API key rotation: You couldn't easily rotate credentials between requests without stopping and reconfiguring the entire package.
  • Hard to test: Global state makes unit tests fragile and dependent on execution order.

In v4: this global state is replaced with explicit clients:

client := hfgo.NewClient(hfgo.WithAPIKey("my-key"))
resp, err := client.Chat().Complete(req, hfgo.WithContext(ctx), hfgo.WithModel("zai-org/GLM-5.1"))

This has several advantages:

  • Individual configurations: Each client holds its own settings. Run multiple clients with different API keys, base URLs, or defaults simultaneously without interference.
  • Easy API key rotation: Swap credentials by creating a new client — no global state mutation, no impact on in-flight requests.
  • Inherent concurrency safety: Because clients don't mutate, callers can safely pass them between goroutines with no locks or synchronization.
  • Testability: Dependency injection becomes natural — each test can create its own client with predictable, isolated configuration.

New: Clients

Clients are immutable value types that hold all your configuration:

  • API key
  • Base URL
  • Default model
  • Custom HTTP client factory
  • Any other options you need

Once created, a client's settings are locked in. If you need different default settings, you create a new client. This design eliminates concurrency issues entirely — callers can safely pass clients between goroutines with no locks or synchronization.

client1 := hfgo.NewClient(hfgo.WithAPIKey("key1"))
client2 := hfgo.NewClient(hfgo.WithAPIKey("key2"), hfgo.WithModel("zai-org/GLM-5.1"))
// Both clients can be used concurrently without any synchronization

New: Services

Services are lightweight, immutable snapshots of client configuration for a given endpoint. When you call Chat(), Raw(), etc. on a client, you get back a service value that remembers the client's settings:

resp, err := client.Chat().Complete(req)

Services are cheap to create, so generally avoid caching them. Just create them as needed. This design keeps the API clean while preserving immutability.

New: Composable Options pattern

Sometimes you want to override a setting just for one call without creating a new client:

// Use the client's default model
resp, err := client.Chat().Complete(req, hfgo.WithContext(ctx))

// Override just for this call
resp, err := client.Chat().Complete(req, hfgo.WithContext(ctx), hfgo.WithModel("zai-org/GLM-5.1"))

How it works: Options follow a clear precedence: client-level options (lowest priority) → request-level options → request struct fields (highest priority, where applicable). This means you can layer defaults and overrides:

client := hfgo.NewClient(hfgo.WithAPIKey("key"), hfgo.WithModel("default-model"))

// Request with client defaults
resp, err := client.Chat().Complete(req)

// Override using options
resp, err := client.Chat().Complete(req, hfgo.WithModel("override-model"))

// Override with struct field (if present), which has the highest priority
req := &hfgo.ChatRequest{
    Model: "struct-field-model", // This value wins if present
}
resp, err := client.Chat().Complete(req, hfgo.WithModel("override-model"))

Note: Struct field precedence only applies where those fields are defined on the request type.

This pattern keeps clients/services immutable and configuration explicit.

New: Custom Error Types

In v3, errors were just error interface values — hard to tell what went wrong.

In v4, we have:

  • APIError: Errors returned by the Hugging Face API (bad auth, rate limits, etc.).
  • SDKError: Errors from the SDK itself (validation failures, serialization errors, etc.).

Callers can type-assert errors as *hfgo.APIError or *hfgo.SDKError rather than treating them as generic errors.

New: Request/Response Structs with Validation

Request and response types generally match the upstream API, with intentional modifications where needed to enforce union types.

Type-safety through union types: The upstream API sometimes uses loose patterns that allow ambiguous or invalid states. In v4, we use custom marshal logic to detect these at request time and return an error. For example, in the Chat endpoint:

  • Message content: The upstream allows content to be either a string or an array of content blocks. In v4, we use a union type that prevents callers from accidentally setting both, or setting neither — validation catches these mistakes when the request is marshaled to JSON.

Validation: Request validation happens during JSON marshaling, catching errors early and making them explicit rather than letting invalid requests hit the API.

Improved Testability Through Dependency Injection

In v3: Testing was painful because of global state. Tests had to manage shared APIKey variables, risking conflicts and flaky tests. Mocking the HTTP layer was difficult and fragile.

In v4: Clients make dependency injection natural. Each test creates its own isolated client with injected dependencies — no shared state, no conflicts. The HTTP client itself is injectable via WithHTTPClientFactory, giving full control over network behavior in tests for callers.

This design enables tests to be cleanly separated into unit and integration tests:

  • Unit tests use mock HTTP transports for fast, isolated testing.
  • Integration tests use real credentials and hit the actual API.

Run them separately with build tags (go test ./... vs. go test -tags=integration ./...), so fast tests run frequently and slow tests only when needed. Integration tests require an API key.

New: Streaming

v4 supports server-sent events (SSE) for real-time responses.


How to Contribute

  • Fork the repo and check out v4-draft from your fork
  • Please use new patterns!
    • Clients create service instances (e.g., ChatService) for each API endpoint
    • Follow Option hierarchy: struct fields (highest precedence) > request opts > client opts (lowest precedence)
    • Keep Clients and Services immutable
    • Use table-driven tests when appropriate and try to maximize signal-to-noise ratio
  • Open a PR against the v4-draft branch
  • Leave a comment here or open an issue if you want to brainstorm features or improvements to avoid duplicated work
  • PRs, docs, and test coverage welcome!

Thanks for reading! I'm excited to see what people build.

Link to v4-draft branch

Guida contributor