Documentation
¶
Overview ¶
Package modelnexus runs a local LLM inside your own process.
chat, err := modelnexus.Open("model.gguf")
defer chat.Close()
resp, err := chat.Infer(modelnexus.Request{
Messages: []modelnexus.Message{{Role: "user", Content: "hello"}},
})
fmt.Println(resp.Text)
No server, no subprocess, no port -- and no cgo: the native bridge is loaded at runtime with purego, so CGO_ENABLED=0 and cross-compilation both keep working.
Index ¶
- Constants
- func CacheDir() (string, error)
- func Fetch() (string, error)
- func PlatformKey() string
- func ReuseCacheOff() *bool
- func ReuseCacheOn() *bool
- func SetLogHandler(fn func(level LogLevel, text string)) error
- func SetLogLevel(level LogLevel) error
- func Version() (string, error)
- type Adapter
- type CacheState
- type Chat
- func (c *Chat) CacheStatus() (CacheState, error)
- func (c *Chat) ClearCache() (CacheState, error)
- func (c *Chat) ClearLoRAs() error
- func (c *Chat) Close() error
- func (c *Chat) CountTokens(req Request) (TokenCount, error)
- func (c *Chat) Infer(req Request) (*Response, error)
- func (c *Chat) InferContext(ctx context.Context, req Request) (*Response, error)
- func (c *Chat) InferStream(req Request, onToken func(piece string) bool) (*Response, error)
- func (c *Chat) InferStreamContext(ctx context.Context, req Request, onToken func(piece string) bool) (*Response, error)
- func (c *Chat) LoRAs() ([]Adapter, error)
- func (c *Chat) LoadLoRA(path string, scale float64) (int, error)
- func (c *Chat) RemoveLoRA(id int) error
- func (c *Chat) SetLoRAScale(id int, scale float64) error
- type EmbedOptions
- type Embedder
- type ErrNativeLibraryNotFound
- type Error
- type LogLevel
- type Message
- type ModelInfo
- type Option
- type Pooling
- type Request
- type RerankHit
- type Response
- type TokenCount
- type Tool
- type ToolCall
- type ToolFunction
- type Usage
Constants ¶
const BridgeVersion = "0.2.1"
BridgeVersion is the modelnexus C ABI this binding speaks.
It is part of the cache path, and that is not cosmetic. The natives release is keyed on the llama.cpp tag (ADR-0004), but the BRIDGE moves independently: 0.2.0 added entry points against the same llama.cpp b9371. Keyed on LlamaTag alone, a user who fetched 0.1.0 natives would keep a cache that looks valid forever and would never re-fetch — a new binding silently loading an old library.
const FinishCancelled = "cancelled"
FinishCancelled is the FinishReason of a generation a consumer stopped.
A cancelled generation is a RESULT, not an error: the response carries the text produced so far and honest usage counts, because those tokens were really generated. Nothing here turns it into a Go error -- the caller decides.
const LlamaTag = "b9371"
LlamaTag is the llama.cpp release this binding's native library is built against.
It must match core/build.sh's pin: the natives are published in a GitHub release keyed by this tag, and a mismatch means Fetch downloads a library the bindings were never tested with.
Variables ¶
This section is empty.
Functions ¶
func CacheDir ¶
CacheDir reports where Fetch stores downloaded natives.
Honours MODELNEXUS_CACHE, else the OS user cache directory.
func Fetch ¶
Fetch downloads this platform's native library into the cache, if it is not already there, and returns the directory holding it.
Go is the one binding that does not receive the library from its package manager: a Go module is a source tree, and embedding five platforms' binaries would make every `go get` pull ~70 MB of libraries the user will not use (ADR-0007). So the library is fetched once, at the user's explicit request, rather than smuggled into the module.
The returned directory is also searched automatically on the next Open, so the usual shape is simply:
if _, err := modelnexus.Fetch(); err != nil { ... }
chat, err := modelnexus.Open("model.gguf")
You do not have to pass the path anywhere.
Fetch is a convenience, not a requirement. Setting MODELNEXUS_LIB to a directory you already have — an air-gapped machine, a vendored copy, a build from core/build.sh — works and skips this entirely.
func PlatformKey ¶
func PlatformKey() string
PlatformKey is the os-arch key used for the staged native directory layout.
func ReuseCacheOff ¶ added in v0.2.0
func ReuseCacheOff() *bool
ReuseCacheOff is a convenience for Request.ReuseCache, which needs an address.
func ReuseCacheOn ¶ added in v0.2.0
func ReuseCacheOn() *bool
ReuseCacheOn is a convenience for Request.ReuseCache. It states the core's default explicitly, which is worth doing in a test that is about the flag.
func SetLogHandler ¶
SetLogHandler routes engine log output to fn instead of stderr. Passing nil restores stderr.
func SetLogLevel ¶
SetLogLevel sets how much the engine logs.
Call it before loading a model: llama.cpp starts logging during load, so afterwards is too late to silence it.
Types ¶
type CacheState ¶ added in v0.2.0
type CacheState struct {
// Tokens is how much of the cache is resident, AFTER the operation that reported
// it -- so a clear always reports 0, and a caller can assert rather than assume.
Tokens int `json:"tokens"`
// NCtx is the engine's context window, so a caller can compare the two.
NCtx int `json:"n_ctx"`
}
CacheState is what the engine's KV cache holds, and the window it holds it in.
type Chat ¶
type Chat struct {
// contains filtered or unexported fields
}
Chat is a loaded model and its inference context. Close it when done.
func Open ¶
Open loads a GGUF model and creates an inference engine.
Models whose chat template cannot do tool calling are rejected here rather than silently degraded -- a deliberate contract inherited from the core.
func (*Chat) CacheStatus ¶ added in v0.2.0
func (c *Chat) CacheStatus() (CacheState, error)
CacheStatus reports what the engine's KV cache currently holds. It changes nothing.
func (*Chat) ClearCache ¶ added in v0.2.0
func (c *Chat) ClearCache() (CacheState, error)
ClearCache drops the KV cache, freeing its memory and forgetting the sequence, and returns the state afterwards -- which is always zero tokens, so a caller can assert the release happened rather than trust that it did.
Prefix reuse is right for a conversation that appends and wrong when a Chat moves to unrelated work: the old conversation keeps occupying context memory, and two tenants sharing a handle would share a cache. Passing ReuseCacheOff() on the next inference also clears, but only as a side effect of doing work -- no help when the point is to release memory now, or to prove the cache is empty before handing the handle on.
func (*Chat) ClearLoRAs ¶
ClearLoRAs unloads every adapter, returning the model to its base behaviour.
func (*Chat) CountTokens ¶ added in v0.2.0
func (c *Chat) CountTokens(req Request) (TokenCount, error)
CountTokens reports what a request's messages will cost, without generating.
It applies the model's chat template and tokenizes; it creates no context, decodes nothing, and does not disturb the KV cache, so it is safe to call between inferences. Every generation parameter on req is ignored.
This is an ABI call rather than a Go helper because counting needs both the model's vocabulary and its parsed chat template, and a binding holds neither.
func (*Chat) InferContext ¶ added in v0.2.0
InferContext is Infer, stopped by cancelling ctx.
The ABI's only cancellation mechanism is the token callback's return value, so ctx is checked once per decoded token: cancellation takes effect before the NEXT token, not mid-token. A cancelled run returns a normal response with FinishCancelled and a nil error -- the tokens were really generated and the usage counts are real, and throwing that away to return ctx.Err() would lose the one thing the caller was billed for. Check Response.Cancelled, or ctx.Err(), to tell the two apart.
func (*Chat) InferStream ¶
InferStream runs one turn, calling onToken with each decoded piece as it is produced. The complete response is still returned when generation finishes.
onToken returns whether to KEEP GOING: return false to stop generation, which ends the turn with FinishCancelled and a complete response carrying the text produced so far. Stopping is a result, not an error.
func (*Chat) InferStreamContext ¶ added in v0.2.0
func (c *Chat) InferStreamContext(ctx context.Context, req Request, onToken func(piece string) bool) (*Response, error)
InferStreamContext is InferStream, additionally stopped by cancelling ctx. Either the callback returning false or ctx being cancelled ends generation.
func (*Chat) LoadLoRA ¶
LoadLoRA loads a LoRA adapter and applies it, returning its id.
Several adapters can be active at once; they apply in load order. Adapters change *behaviour* -- output format, tone, tool-call reliability -- not knowledge. For facts, retrieve.
func (*Chat) RemoveLoRA ¶
RemoveLoRA unloads one adapter and reapplies the rest.
type EmbedOptions ¶
type EmbedOptions struct {
// Pooling strategy. Empty means the model's own default. Use PoolingRank for a
// reranker -- Rerank refuses to run without it.
Pooling Pooling
// NCtx is the context size; 0 means the model default.
NCtx int
// NBatch caps how many tokens one input may have; 0 means the core's default
// (llb_embed_create in core/include/llamabridge.h states it).
NBatch int
// OnEvent receives lifecycle events during load.
OnEvent func(string)
}
EmbedOptions configures OpenEmbedder.
type Embedder ¶
type Embedder struct {
// contains filtered or unexported fields
}
Embedder is a model loaded for embedding or reranking.
Separate from Chat on purpose: embedding needs a context created with embeddings enabled and a pooling type fixed up front, and reranking needs PoolingRank specifically -- none of which can be switched on a generation context afterwards.
func OpenEmbedder ¶
func OpenEmbedder(ggufPath string, opts *EmbedOptions) (*Embedder, error)
OpenEmbedder loads a model for embedding or reranking.
func (*Embedder) Embed ¶
Embed returns one vector per input, in input order.
Vectors are L2-normalized, which is what makes a dot product a cosine similarity.
type ErrNativeLibraryNotFound ¶
ErrNativeLibraryNotFound reports that the native bridge could not be located or loaded.
Go is the one binding that does not receive the shared library from a package manager -- purego resolves it at runtime -- so this failure mode is unique to Go and is surfaced as a distinct typed error rather than a panic. See ADR-0002 and the model-core spec.
func (*ErrNativeLibraryNotFound) Error ¶
func (e *ErrNativeLibraryNotFound) Error() string
func (*ErrNativeLibraryNotFound) Unwrap ¶
func (e *ErrNativeLibraryNotFound) Unwrap() error
type Error ¶
Error is a failure reported by the core.
Code is stable and identical across every language binding; Message is for humans.
type LogLevel ¶
type LogLevel int32
LogLevel is how much the inference engine is allowed to say.
The bridge defaults to LogWarn rather than llama.cpp's own default: a library embedded in someone else's process should be quiet unless asked.
type Message ¶
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
ToolCallID string `json:"tool_call_id,omitempty"`
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
}
Message is one turn of a conversation.
type ModelInfo ¶
type ModelInfo struct {
SupportsTools bool `json:"supports_tools"`
SupportsToolCalls bool `json:"supports_tool_calls"`
HasToolUseTemplate bool `json:"has_tool_use_template"`
ChatFormat string `json:"chat_format"`
Error string `json:"error"`
}
ModelInfo reports a model's tool-calling capability.
type Option ¶
type Option func(*openConfig)
Option configures Open.
func WithBatchSize ¶ added in v0.2.0
WithBatchSize sets the logical batch size. Unset means the core's default.
func WithContextSize ¶ added in v0.2.0
WithContextSize sets the engine's context window in tokens. Unset means the core's default (llb_chat_create in core/include/llamabridge.h states it).
This is create-time: it is the size of the KV cache, and it cannot be changed on a live engine.
func WithEventHandler ¶
WithEventHandler receives the core's progress events during load and inference.
func WithMaxSequences ¶ added in v0.2.0
WithMaxSequences reserves room for n concurrent sequences. Unset means the core's default.
It has no observable effect today. It is accepted now because it is a create-time parameter -- the one kind that cannot be added later through request JSON -- so reserving it is what lets multi-sequence slots arrive without an ABI break (ADR-0008 D6).
type Pooling ¶
type Pooling string
Pooling is how token vectors are reduced to one vector per input.
const ( // PoolingMean averages token vectors -- the usual choice for sentence embeddings. PoolingMean Pooling = "mean" // PoolingCLS takes the first token's vector, used by BERT-style encoders. PoolingCLS Pooling = "cls" // PoolingLast takes the final token's vector, used by decoder-style embedders. PoolingLast Pooling = "last" // PoolingRank attaches the model's classification head. Required for Rerank, // and useless for anything else. PoolingRank Pooling = "rank" // PoolingNone returns no pooled vector at all. PoolingNone Pooling = "none" )
type Request ¶
type Request struct {
Messages []Message `json:"messages"`
Tools []Tool `json:"tools,omitempty"`
ToolChoice string `json:"tool_choice,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
TopK *int `json:"top_k,omitempty"`
TopP *float64 `json:"top_p,omitempty"`
MinP *float64 `json:"min_p,omitempty"`
MaxTokens *int `json:"max_tokens,omitempty"`
RepeatPenalty *float64 `json:"repeat_penalty,omitempty"`
Seed *uint32 `json:"seed,omitempty"`
Stop []string `json:"stop,omitempty"`
// JSONSchema constrains the output to a JSON Schema. The returned Text is
// guaranteed to parse as JSON: the grammar llama.cpp derives from a schema
// deliberately permits a “`json fence, and the core strips it before returning.
//
// Any value that marshals to a JSON Schema object works -- but PREFER
// json.RawMessage, and read the next paragraph before reaching for a map.
//
// encoding/json SORTS map keys, so a schema built as map[string]any reaches the
// model with its properties in alphabetical order. Under grammar-constrained
// decoding property order is load-bearing: the model must emit the fields in the
// order the grammar allows, so it commits to a "rating" before it has reasoned out
// a "sentiment" it would otherwise have written first. This is not theoretical --
// the same prompt at the same seed returned rating 2 from Go and rating 3 from
// Python and JS, purely because Go had reordered the two properties. The output
// still parses, still validates, and is quietly a different answer.
//
// json.RawMessage preserves the order you wrote:
//
// JSONSchema: json.RawMessage(`{
// "type": "object",
// "properties": {
// "sentiment": {"type": "string"},
// "rating": {"type": "integer"}
// },
// "required": ["sentiment", "rating"]
// }`)
//
// A struct with ordered fields works too; a map does not, and cannot be made to.
JSONSchema any `json:"json_schema,omitempty"`
// Grammar constrains the output to a raw GBNF grammar.
//
// Setting this together with JSONSchema is rejected by the core with
// INVALID_REQUEST rather than resolved by precedence: a silent winner between
// two output constraints is a debugging session nobody should have.
Grammar string `json:"grammar,omitempty"`
// ReuseCache controls KV prefix reuse across calls on the same Chat. The core's
// default is true, which is why this is a pointer -- leaving it nil means "the
// core decides", and a plain bool would silently opt every caller out.
//
// Reuse is purely a latency property; output is identical either way. Set it to
// false when calls must be provably independent (a determinism harness, or
// tenants sharing one handle).
ReuseCache *bool `json:"reuse_cache,omitempty"`
}
Request is one inference call.
Generation parameters are pointers so that "unset" is distinguishable from the zero value -- a Temperature of 0 is a legitimate, and very different, request from "use the default".
type RerankHit ¶
RerankHit is one scored document.
Index is the document's position in the ORIGINAL slice, because results come back reordered. Score is a raw model logit: comparable within one call, not across models, and not a probability.
type Response ¶
type Response struct {
Type string `json:"type"`
Text string `json:"text"`
ToolCalls []ToolCall `json:"tool_calls"`
FinishReason string `json:"finish_reason"`
Usage Usage `json:"usage"`
}
Response is the result of one inference call.
type TokenCount ¶ added in v0.2.0
type TokenCount struct {
// Tokens is the prompt length after the model's chat template is applied.
Tokens int `json:"tokens"`
// NCtx is the engine's context window, so a caller can compare the two.
NCtx int `json:"n_ctx"`
}
TokenCount is what a message list will cost, without running inference.
type Tool ¶
type Tool struct {
Type string `json:"type"`
Function ToolFunction `json:"function"`
}
Tool is a function the model may call, in OpenAI's schema shape.
type ToolCall ¶
type ToolCall struct {
ID string `json:"id"`
Name string `json:"name"`
Arguments string `json:"arguments"`
}
ToolCall is a call the model proposes. It is never executed here -- modelnexus emits OpenAI-shaped tool calls and stops. Executing them is the caller's job (ADR-0003).
type ToolFunction ¶
type ToolFunction struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Parameters any `json:"parameters,omitempty"`
}
ToolFunction describes one callable.