Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/platform/inference/client.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 1e9249840a4302ec82e123b9c9cb6f75badea3e4 |
| Branch | master |
| Path or archive member | internal/platform/inference/client.go |
| Original SHA-256 | d8119e77150ced05173e2aedbb7b4e88c6489362ad9ca6b533dd24ea7429c630 |
| Git blob SHA | 417c1c5c5f4b402f0e261161ff0cd476001d1147 |
| Reading-copy SHA-256 | d8119e77150ced05173e2aedbb7b4e88c6489362ad9ca6b533dd24ea7429c630 |
Every recorded occurrence
These 41 records contain identical original source bytes. Their paths and revisions remain part of the archive.
| Original record | Branch or archive | Revision / identifier | Original location |
|---|---|---|---|
| Janady13/missionmatch / internal/platform/inference/client.go | master | 1e9249840a4302ec82e123b9c9cb6f75badea3e4 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | m5-price-to-win | 41000ad30fff4129fdaf5ec84e15ba14c27d4890 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | ws3-clearance | 4b8e7ce594c068621867f9c9deed0ec64ae6c553 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | collision-deconfliction | 4ee5a09972608ed89f8377c6665542d5f450f0a6 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | ws7-federal-signal | 6a9f6ce9cfb13345ffb47770d4960e7c2d9c17c2 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | ws6-retrieval | 6c56e29231ec05735927fa265c97d517499dfeaf | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | per-tenant-routing | 9bde0f9a74b89d01980fa658e5253acda7528d0c | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | ai-brain-live | aac32fc49c878cc5bca099459307611562d0bac1 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | graphrag-foundation | b0ddeb9f633fd138d09309d1a17b7d13bc3a67f7 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | ws1-ontology-sync | b596f449a32a1016fbd403c36505cc00a1ac9ee6 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | time-to-fill-survival | b66e1e505863d4f17741a4b9ede73509e45bd6c6 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | mag-substrate | b82c7100a492dec67dfd1eda6b3f53def859be35 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | capture-readiness | bae6af7cb2997e819174a9e9cfff5e16067a1975 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | deploy-and-ops | bdacd9d0b4d5998d25fc74bf4007f4c3ec538c7a | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | ws8-teaching-loop | c823f0fb6bb2aba0f39001eb6de96f4000d9321f | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | phase2-eval-fairness | d361af254701975fe239927656ababb56374655e | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | phase2-connectors | e038d4c687aec16f8bd9bc4d907eea14d91f8704 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | ws2-alias-relationship | e4dada4601c6eb101830f9d223a9655dfa120616 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | presentation-readiness | eb574acaa29d71ce4d0f852572e87c275c79fdb7 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | ws9-bench-ingestion | f585a9ef32fa554663ce1fba9a414a54b6e49cf0 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | vendor-intelligence | fe9796a19c261951d3f2565206c0c1b5ac294465 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | per-tenant-routing | 9bde0f9a74b89d01980fa658e5253acda7528d0c | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | collision-deconfliction | 4ee5a09972608ed89f8377c6665542d5f450f0a6 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | ws6-retrieval | 6c56e29231ec05735927fa265c97d517499dfeaf | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | m5-price-to-win | 41000ad30fff4129fdaf5ec84e15ba14c27d4890 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | ws1-ontology-sync | b596f449a32a1016fbd403c36505cc00a1ac9ee6 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | master | 1e9249840a4302ec82e123b9c9cb6f75badea3e4 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | ws8-teaching-loop | c823f0fb6bb2aba0f39001eb6de96f4000d9321f | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | mag-substrate | b82c7100a492dec67dfd1eda6b3f53def859be35 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | ws3-clearance | 4b8e7ce594c068621867f9c9deed0ec64ae6c553 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | phase2-eval-fairness | d361af254701975fe239927656ababb56374655e | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | time-to-fill-survival | b66e1e505863d4f17741a4b9ede73509e45bd6c6 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | graphrag-foundation | b0ddeb9f633fd138d09309d1a17b7d13bc3a67f7 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | deploy-and-ops | bdacd9d0b4d5998d25fc74bf4007f4c3ec538c7a | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | ws9-bench-ingestion | f585a9ef32fa554663ce1fba9a414a54b6e49cf0 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | ws2-alias-relationship | e4dada4601c6eb101830f9d223a9655dfa120616 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | presentation-readiness | eb574acaa29d71ce4d0f852572e87c275c79fdb7 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | ws7-federal-signal | 6a9f6ce9cfb13345ffb47770d4960e7c2d9c17c2 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | ai-brain-live | aac32fc49c878cc5bca099459307611562d0bac1 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | phase2-connectors | e038d4c687aec16f8bd9bc4d907eea14d91f8704 | Open original |
| Janady13/missionmatch / internal/platform/inference/client.go | capture-readiness | bae6af7cb2997e819174a9e9cfff5e16067a1975 | Open original |
Recovered source transcript
Download complete source text ↓230 lines · 8.4 KB
// Package inference is the typed Go client for the shared, stateless MEGAMIND inference plane. It is how
// the FIPS API binary reaches embeddings, reranking, generation, and faithfulness checking WITHOUT
// hosting any ML in process: the heavy models run out of process on the inference plane (Apple Silicon,
// Metal), and this client speaks plain HTTP/JSON to them with timeouts, bounded retries, and a circuit
// breaker so a sick plane degrades the product gracefully instead of hanging it.
//
// The client sends no client identifier to the plane on the inference paths (it sends text in, gets
// vectors or text out), and the one teaching path is gated by the scrub boundary, so MEGAMIND learns
// only the craft of the work and persists nothing about any tenant.
package inference
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"time"
"github.com/missionworkforce/missionmatch/internal/platform/scrub"
)
// Config configures the client. The model names and dimension come from server side config so a model
// swap (for example all-minilm to bge-small-en-v1.5, both 384) is a config change, not a code change.
type Config struct {
BaseURL string // MEGAMIND gateway base, e.g. http://100.94.8.94:8090
EmbedModel string // 384 dim embedder (all-minilm or bge-small-en-v1.5)
RerankModel string // cross encoder (bge-reranker-v2-m3) or cosine fallback
GenerateModel string // local instruct model (e.g. llama3.1:8b)
Dimension int // expected embedding width; a mismatch is a hard error (column is vector(384))
Timeout time.Duration // per request
MaxRetries int // additional attempts on a transient failure
BreakerTrips int // consecutive failures before the breaker opens
BreakerCool time.Duration // how long the breaker stays open
TeachingEnabled bool // when false, Teach validates the boundary but does not transmit
}
// Client talks to the MEGAMIND plane. It is safe for concurrent use.
type Client struct {
cfg Config
http *http.Client
cb *breaker
}
// New builds a client. BaseURL is required; sensible defaults fill the rest.
func New(cfg Config) (*Client, error) {
if cfg.BaseURL == "" {
return nil, errors.New("inference: BaseURL is required")
}
if cfg.Dimension <= 0 {
cfg.Dimension = 384
}
if cfg.Timeout <= 0 {
cfg.Timeout = 20 * time.Second
}
if cfg.BreakerTrips <= 0 {
cfg.BreakerTrips = 5
}
if cfg.BreakerCool <= 0 {
cfg.BreakerCool = 30 * time.Second
}
return &Client{
cfg: cfg,
http: &http.Client{Timeout: cfg.Timeout},
cb: newBreaker(cfg.BreakerTrips, cfg.BreakerCool),
}, nil
}
// Health pings the plane.
func (c *Client) Health(ctx context.Context) error {
return c.do(ctx, http.MethodGet, "/healthz", nil, nil)
}
// Embed returns one 384 dim vector per input. It hard fails if the plane returns the wrong width, since a
// width mismatch would silently corrupt the pgvector(384) columns.
func (c *Client) Embed(ctx context.Context, inputs []string) (*EmbedResponse, error) {
if len(inputs) == 0 {
return &EmbedResponse{Model: c.cfg.EmbedModel, Dimension: c.cfg.Dimension, Embeddings: nil}, nil
}
var out EmbedResponse
if err := c.do(ctx, http.MethodPost, "/v1/embed", EmbedRequest{Model: c.cfg.EmbedModel, Inputs: inputs}, &out); err != nil {
return nil, err
}
if len(out.Embeddings) != len(inputs) {
return nil, fmt.Errorf("inference: embed returned %d vectors for %d inputs", len(out.Embeddings), len(inputs))
}
for i, v := range out.Embeddings {
if len(v) != c.cfg.Dimension {
return nil, fmt.Errorf("inference: embedding %d has width %d, expected %d (the column is vector(%d))", i, len(v), c.cfg.Dimension, c.cfg.Dimension)
}
}
return &out, nil
}
// Rerank scores documents against a query, highest first.
func (c *Client) Rerank(ctx context.Context, query string, documents []string, topN int) (*RerankResponse, error) {
var out RerankResponse
if err := c.do(ctx, http.MethodPost, "/v1/rerank", RerankRequest{Model: c.cfg.RerankModel, Query: query, Documents: documents, TopN: topN}, &out); err != nil {
return nil, err
}
return &out, nil
}
// Generate runs one stateless generation.
func (c *Client) Generate(ctx context.Context, req GenerateRequest) (*GenerateResponse, error) {
if req.Model == "" {
req.Model = c.cfg.GenerateModel
}
var out GenerateResponse
if err := c.do(ctx, http.MethodPost, "/v1/generate", req, &out); err != nil {
return nil, err
}
return &out, nil
}
// CheckFaithfulness asks the plane whether an answer is supported by its evidence (the self correction gate).
func (c *Client) CheckFaithfulness(ctx context.Context, answer string, evidence []string) (*FaithfulnessResponse, error) {
var out FaithfulnessResponse
if err := c.do(ctx, http.MethodPost, "/v1/faithfulness", FaithfulnessRequest{Answer: answer, Evidence: evidence}, &out); err != nil {
return nil, err
}
return &out, nil
}
// Teach sends a contents scrubbed craft signal to the plane's teaching channel. The boundary is enforced
// here (scrub.Marshal refuses any client fact) AND again on the gateway. When TeachingEnabled is false
// the signal is validated but not transmitted, which is the default posture until multiple live clients
// exist (the federated loop is scaffolded, not active).
func (c *Client) Teach(ctx context.Context, signal scrub.TeachingSignal) error {
body, err := scrub.Marshal(signal) // refuses to emit a payload carrying a client fact
if err != nil {
return err
}
if !c.cfg.TeachingEnabled {
return nil // boundary checked, transmission disabled by posture
}
return c.doRaw(ctx, http.MethodPost, "/v1/teach", body, nil)
}
// do marshals req (if any), runs the request through the breaker with bounded retries, and decodes resp.
func (c *Client) do(ctx context.Context, method, path string, req any, resp any) error {
var body []byte
if req != nil {
var err error
if body, err = json.Marshal(req); err != nil {
return err
}
}
return c.doRaw(ctx, method, path, body, resp)
}
// ErrPlaneUnavailable marks that the inference plane could not be reached after retries (a network error,
// a timeout, or a 5xx) even though the circuit breaker has not opened yet. The caller maps it to a 503, so a
// plane outage degrades to a typed error, never a generic 4xx and never a hang.
var ErrPlaneUnavailable = errors.New("inference: plane unavailable")
func (c *Client) doRaw(ctx context.Context, method, path string, body []byte, resp any) error {
if !c.cb.allow() {
return ErrCircuitOpen
}
var lastErr error
for attempt := 0; attempt <= c.cfg.MaxRetries; attempt++ {
if attempt > 0 {
// exponential backoff, cancellable
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(time.Duration(attempt) * 150 * time.Millisecond):
}
}
err := c.attempt(ctx, method, path, body, resp)
if err == nil {
c.cb.record(true)
return nil
}
lastErr = err
if !transient(err) {
c.cb.record(false)
return err
}
}
c.cb.record(false)
// All retries exhausted on transient failures without tripping the breaker yet: a plane outage. Wrap the
// typed sentinel so the caller returns a 503, never a generic 4xx and never a hang.
return fmt.Errorf("%w: %s %s failed after %d attempts: %v", ErrPlaneUnavailable, method, path, c.cfg.MaxRetries+1, lastErr)
}
func (c *Client) attempt(ctx context.Context, method, path string, body []byte, resp any) error {
var r io.Reader
if body != nil {
r = bytes.NewReader(body)
}
httpReq, err := http.NewRequestWithContext(ctx, method, c.cfg.BaseURL+path, r)
if err != nil {
return err
}
if body != nil {
httpReq.Header.Set("Content-Type", "application/json")
}
res, err := c.http.Do(httpReq)
if err != nil {
return &transientError{err}
}
defer func() { _, _ = io.Copy(io.Discard, res.Body); _ = res.Body.Close() }()
if res.StatusCode >= 500 {
return &transientError{fmt.Errorf("inference: plane returned %d", res.StatusCode)}
}
if res.StatusCode >= 400 {
snippet, _ := io.ReadAll(io.LimitReader(res.Body, 512))
return fmt.Errorf("inference: %s %s: status %d: %s", method, path, res.StatusCode, bytes.TrimSpace(snippet))
}
if resp != nil {
return json.NewDecoder(res.Body).Decode(resp)
}
return nil
}
// transientError marks a failure as retryable (network or 5xx).
type transientError struct{ err error }
func (e *transientError) Error() string { return e.err.Error() }
func (e *transientError) Unwrap() error { return e.err }
func transient(err error) bool {
var t *transientError
return errors.As(err, &t)
}