Files
zeekay 6f13ef421a pkg/miner: pluggable inference-engine backend interface
Close three `// TODO: Integrate with actual ...` stubs in
pkg/miner/miner.go (runInference, runChat, runEmbedding) by introducing
a small pluggable backend interface. The miner now dispatches through
backend.InferenceBackend rather than inlining placeholder output.

Two reference backends ship in-tree:

- pkg/miner/backend/noop  — deterministic mock, byte-for-byte identical
  to the pre-refactor inline stubs. Default. Zero config, zero deps.
- pkg/miner/backend/openai — OpenAI-compatible HTTP adapter built on
  net/http. Covers llama.cpp, vllm, ollama, LocalAI, and api.openai.com
  via OPENAI_API_BASE. No new go.mod dependencies.

Config gains Backend / OpenAIBase / OpenAIAPIKey / OpenAIModel /
OpenAIEmbeddingModel fields. Unknown backend values fall back to noop so
operator typos don't crash-loop the miner. Existing miner_test.go passes
unchanged; DefaultConfig() behaviour is preserved.

Also wire a GPUStatsProvider hook on Miner so GetStats can surface
best-effort GPU telemetry (GPUUtilization, MemoryUsed) without pulling
NVML/MLX bindings into the main binary. Default is nil = no-op, matching
current behaviour; a follow-up PR will wire a real provider.

Closes luxfi/ai#1.

Contributed by kcolbchain (https://kcolbchain.com) · https://abhishekkrishna.com
2026-04-21 06:55:21 +05:30

93 lines
2.6 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package noop
import (
"context"
"testing"
"github.com/luxfi/ai/pkg/miner/backend"
)
func TestName(t *testing.T) {
if got := New().Name(); got != "noop" {
t.Errorf("Name: got %q want %q", got, "noop")
}
}
// TestChatMatchesLegacyStub pins the assistant content string so the
// miner-level behaviour is indistinguishable from the pre-refactor inline
// stub in miner.runChat.
func TestChatMatchesLegacyStub(t *testing.T) {
b := New()
resp, err := b.Chat(context.Background(), backend.ChatRequest{Model: "m"})
if err != nil {
t.Fatalf("Chat: %v", err)
}
const want = "I'm an AI assistant running on the Lux network."
if resp.Content != want {
t.Errorf("Chat content: got %q want %q", resp.Content, want)
}
if resp.Role != "assistant" {
t.Errorf("Chat role: got %q want %q", resp.Role, "assistant")
}
if resp.Model != "m" {
t.Errorf("Chat model: got %q want %q", resp.Model, "m")
}
}
// TestInferenceMatchesLegacyStub pins the echo-prompt format used by the
// pre-refactor miner.runInference.
func TestInferenceMatchesLegacyStub(t *testing.T) {
b := New()
resp, err := b.Inference(context.Background(), backend.InferenceRequest{
Model: "m",
Prompt: "what time is it?",
})
if err != nil {
t.Fatalf("Inference: %v", err)
}
const want = "Response to: what time is it?"
if resp.Text != want {
t.Errorf("Inference text: got %q want %q", resp.Text, want)
}
if resp.Tokens != 10 {
t.Errorf("Inference tokens: got %d want 10", resp.Tokens)
}
}
// TestEmbedMatchesLegacyStub pins the zero-vector behaviour and the 384 dim.
func TestEmbedMatchesLegacyStub(t *testing.T) {
b := New()
resp, err := b.Embed(context.Background(), backend.EmbedRequest{Model: "m", Text: "x"})
if err != nil {
t.Fatalf("Embed: %v", err)
}
if len(resp.Embedding) != DefaultEmbeddingDims {
t.Errorf("Embed dims: got %d want %d", len(resp.Embedding), DefaultEmbeddingDims)
}
for i, v := range resp.Embedding {
if v != 0.0 {
t.Errorf("Embed[%d]: got %v want 0.0", i, v)
break
}
}
}
// TestCustomEmbeddingDims checks the dim knob (used by callers that want to
// mimic a real model's output shape in tests).
func TestCustomEmbeddingDims(t *testing.T) {
b := &Backend{EmbeddingDims: 16}
resp, err := b.Embed(context.Background(), backend.EmbedRequest{Text: "x"})
if err != nil {
t.Fatalf("Embed: %v", err)
}
if len(resp.Embedding) != 16 {
t.Errorf("custom dims: got %d want 16", len(resp.Embedding))
}
if caps := b.Capabilities(); caps.EmbeddingDims != 16 {
t.Errorf("Capabilities.EmbeddingDims: got %d want 16", caps.EmbeddingDims)
}
}