From 4d73fa250cc937e71fc79b2dd214a2bef9bebd82 Mon Sep 17 00:00:00 2001 From: Snider Date: Fri, 20 Feb 2026 06:01:30 +0000 Subject: [PATCH] =?UTF-8?q?feat(ml):=20Phase=202=20=E2=80=94=20migrate=20m?= =?UTF-8?q?l=5Fbackends=20to=20go-inference=20registry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- TODO.md | 8 ++++---- go.mod | 2 +- mcp/tools_ml.go | 29 +++++++++++++++++++---------- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/TODO.md b/TODO.md index 1ae23ec..e41f5b0 100644 --- a/TODO.md +++ b/TODO.md @@ -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 diff --git a/go.mod b/go.mod index 77525c1..530efb4 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/mcp/tools_ml.go b/mcp/tools_ml.go index 1fa6526..b17f26b 100644 --- a/mcp/tools_ml.go +++ b/mcp/tools_ml.go @@ -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() }