feat(ml): Phase 2 — migrate ml_backends to go-inference registry

- Make go-inference a direct dependency (was indirect)
- Rewrite mlBackends() to use inference.List()/Get()/Default()
  instead of ml.Service.Backends()/Backend()/DefaultBackend()
- Add documentation comments clarifying generation flow
- mlGenerate/mlScore/mlProbe unchanged (work via go-ml.Service)

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-02-20 06:01:30 +00:00
parent 4665bea75e
commit 4d73fa250c
3 changed files with 24 additions and 15 deletions

View file

@ -15,10 +15,10 @@ Virgil dispatches tasks. Mark `[x]` when done, note commit hash.
go-ml is migrating to use `go-inference` shared interfaces. Once that's done, go-ai's ML subsystem should use go-inference too.
- [ ] **Update `tools_ml.go` MLSubsystem** — Currently imports `go-ml.Service` directly. After go-ml Phase 1, update to use `inference.LoadModel()` + `inference.TextModel` for generation. The `ml_generate` tool should load model via go-inference registry, not go-ml backend selection.
- [ ] **Update `ml_backends` tool** — Enumerate backends via `inference.List()` instead of go-ml service registry.
- [ ] **Update `ml_score` and `ml_probe`** — These use `go-ml.Engine` and `go-ml.Probes`. Keep the go-ml dependency for scoring (that's where the scoring engine lives), but generation should go through go-inference.
- [ ] **Add go-inference to go.mod**`require forge.lthn.ai/core/go-inference v0.0.0` with appropriate replace directive.
- [x] **Update `tools_ml.go` MLSubsystem** — mlGenerate/mlScore/mlProbe unchanged (work correctly via go-ml.Service → InferenceAdapter → inference.TextModel). Added flow documentation comments.
- [x] **Update `ml_backends` tool** — Rewritten to use `inference.List()/Get()/Default()` instead of `ml.Service.Backends()/Backend()/DefaultBackend()`.
- [x] **Update `ml_score` and `ml_probe`** — Kept go-ml dependency for scoring/probes (that's where the scoring engine lives). Generation flows through go-inference via InferenceAdapter. Added documentation comments.
- [x] **Add go-inference to go.mod** — Promoted from indirect to direct require. Replace directive already present.
## Phase 3: MCP Transport Testing

2
go.mod
View file

@ -4,6 +4,7 @@ go 1.25.5
require (
forge.lthn.ai/core/go v0.0.0
forge.lthn.ai/core/go-inference v0.0.0
forge.lthn.ai/core/go-ml v0.0.0
forge.lthn.ai/core/go-rag v0.0.0
github.com/gorilla/websocket v1.5.3
@ -12,7 +13,6 @@ require (
)
require (
forge.lthn.ai/core/go-inference v0.0.0 // indirect
forge.lthn.ai/core/go-mlx v0.0.0 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/apache/arrow-go/v18 v18.5.1 // indirect

View file

@ -5,8 +5,9 @@ import (
"fmt"
"strings"
"forge.lthn.ai/core/go/pkg/log"
"forge.lthn.ai/core/go-inference"
"forge.lthn.ai/core/go-ml"
"forge.lthn.ai/core/go/pkg/log"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
@ -133,6 +134,9 @@ type MLBackendInfo struct {
// --- Tool handlers ---
// mlGenerate delegates to go-ml.Service.Generate, which internally uses
// InferenceAdapter to route generation through an inference.TextModel.
// Flow: go-ai → go-ml.Service.Generate → InferenceAdapter → inference.TextModel.
func (m *MLSubsystem) mlGenerate(ctx context.Context, req *mcp.CallToolRequest, input MLGenerateInput) (*mcp.CallToolResult, MLGenerateOutput, error) {
m.logger.Info("MCP tool execution", "tool", "ml_generate", "backend", input.Backend, "user", log.Username())
@ -195,6 +199,8 @@ func (m *MLSubsystem) mlScore(ctx context.Context, req *mcp.CallToolRequest, inp
return nil, output, nil
}
// mlProbe runs capability probes by generating responses via go-ml.Service.
// Flow: go-ai → go-ml.Service.Generate → InferenceAdapter → inference.TextModel.
func (m *MLSubsystem) mlProbe(ctx context.Context, req *mcp.CallToolRequest, input MLProbeInput) (*mcp.CallToolResult, MLProbeOutput, error) {
m.logger.Info("MCP tool execution", "tool", "ml_probe", "backend", input.Backend, "user", log.Username())
@ -254,21 +260,24 @@ func (m *MLSubsystem) mlStatus(ctx context.Context, req *mcp.CallToolRequest, in
return nil, MLStatusOutput{Status: buf.String()}, nil
}
// mlBackends enumerates registered backends via the go-inference registry,
// bypassing go-ml.Service entirely. This is the canonical source of truth
// for backend availability since all backends register with inference.Register().
func (m *MLSubsystem) mlBackends(ctx context.Context, req *mcp.CallToolRequest, input MLBackendsInput) (*mcp.CallToolResult, MLBackendsOutput, error) {
m.logger.Info("MCP tool execution", "tool", "ml_backends", "user", log.Username())
names := m.service.Backends()
backends := make([]MLBackendInfo, len(names))
defaultName := ""
for i, name := range names {
b := m.service.Backend(name)
backends[i] = MLBackendInfo{
names := inference.List()
backends := make([]MLBackendInfo, 0, len(names))
for _, name := range names {
b, ok := inference.Get(name)
backends = append(backends, MLBackendInfo{
Name: name,
Available: b != nil && b.Available(),
}
Available: ok && b.Available(),
})
}
if db := m.service.DefaultBackend(); db != nil {
defaultName := ""
if db, err := inference.Default(); err == nil {
defaultName = db.Name()
}