mllm

package
v0.3.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 31, 2026 License: MIT Imports: 16 Imported by: 0

README

mllm

tiny ai client

Environment Runtime Config

Tested with llama.cpp:

export LLM_MODEL=gemma-4-E2B-it-Q4_K_M LLM_HOST=http://127.0.0.1:9931/v1/chat/completions LLM_API_KEY=some-key-here

Tested with ollama:

export LLM_MODEL=gemma4:26b LLM_HOST=http://127.0.0.1:11434/v1/chat/completions

Usage

Typical usage for TUI would be a background stream with a custom StreamHandler to write into a view that renders markdown.

Background

Use StreamBG to send the request, returning a chan error that is closed when stream is finished.

 client := mllm.Default() // uses env for Model and Host
 think := false
 var chatResp mllm.ChatResponse // has a Handler method that is a StreamHandler func
 var handlers []StreamHandler // stream handlers are called each frame/delta
 handlers = []StreamHandler{
   mllm.NewHandlerOut(os.Stderr, "THINKING:\n", "\nFINISHED THINKING\n"),
   chatResp.Handler,
   // some other handler if needed
 }
 err := <- e.StreamBG(ctx, messages, think, handlers...)
}
Foreground

For non-streaming behavior, use msg, err := client.Get(...)

 client := mllm.Default() // uses env for Model and Host
 think := false
 var resp mllm.Message
 var err error
 resp, err = client.GetMessage(ctx, messages, think)
}

See also: test file

StreamHandler

Although it is not necessary, a new StreamHandler is easy to make:

import "github.com/aerth/mostly/mllm"

// as a function

func Dummy(cr mllm.ChatResponse) error {
	return nil
}

// as a method
type x struct{}

func (x) Dummy(cr mllm.ChatResponse) error {
	return nil
}

Documentation

Overview

Package mllm is simple llm client, part of aerth/mostly project

Index

Constants

This section is empty.

Variables

View Source
var (
	DefaultThinkPre  = "Thinking:\n"
	DefaultThinkPost = "\n... done thinking.\n"
)
View Source
var (
	EnvModel = []string{"MLLM_MODEL", "OLLAMA_MODEL", "LLM_MODEL"}
	EnvHost  = []string{"MLLM_HOST", "OLLAMA_HOST", "LLM_HOST"}
	EnvKey   = []string{"MLLM_API_KEY", "OLLAMA_API_KEY", "LLM_API_KEY"}
)
View Source
var (
	StopAtFirstHandlerError = true
	ErrNoLines              = errors.New("no lines read")
)
View Source
var (
	Errorf = stackerr.Errorf
	Slog   = slog.Default
)
View Source
var (
	HideThink = false
	HideTools = false
	DebugLLM  = os.Getenv("DEBUG_LLM") == "1"
)
View Source
var Default = func() E {
	e := NewE(nil, nil)
	return e
}

Default creates a new reusable LLM endpoint from env using NewEndpoint with no tools no options.

View Source
var DefaultChatMessageFormatter = func(cm ChatMessage) ([]Message, bool) {
	return []Message{cm.Message}, true
}

DefaultChatMessageFormatter pass through ChatMessageFormatter In a custom implementation, this might add text to Content, replace Role, or insert messages before/after.

View Source
var DefaultEmbedOptions map[string]any // nil
View Source
var DefaultWriter io.Writer = nil

DefaultWriter added to all new endpoints

View Source
var EmbedOptions = struct {
	URL        url.URL
	Model      string // if req.Model is empty and env is empty
	Dimensions int
	EnvHost    string // the ENV_VAR to check to override [E.Host] (for when using 2 different hosts for llm,embeddings.)
	Path       string // custom endpoint url path (default /api/embed)
}{
	Model:   "nomic-embed-text:v1.5",
	EnvHost: "LLM_EMBED_HOST",
	Path:    "/v1/embeddings",
}

EmbedOptions application-wide global options

Apart from Path, all serve as fallback if not present in request/env.

View Source
var ErrNoConvo = errors.New("no messages in conversation")
View Source
var ForcePath = false

NewE custom NewEndpoint

View Source
var ThinkRequestType = false
View Source
var ToolWriter = toolWriter

Functions

func StderrHandler

func StderrHandler(cr ChatResponse) error

StderrHandler plain 🍔

func StdoutHandler

func StdoutHandler(cr ChatResponse) error

StdoutHandler plain 🍔

func Summary

func Summary(w io.Writer, st Stats) (err error)

Summary in well-known format

Types

type Call added in v0.3.1

type Call[T any] struct {
	ID       string `json:"id"`
	Index    int    `json:"index"`
	Type     string `json:"type"` // "function" always?
	Function struct {
		Name      string `json:"name"`
		Arguments T      `json:"arguments,omitempty"`
	} `json:"function"`
}

Call is for a tool-runner to decode arguments. It handles the case where Arguments json object is passed as a json string.

func (*Call[T]) UnmarshalJSON added in v0.3.1

func (c *Call[T]) UnmarshalJSON(b []byte) error

type ChatHandlerT

type ChatHandlerT interface {
	HandleChat(cr ChatResponse) error
}

type ChatMessage

type ChatMessage struct {
	MessageID uuid.UUID `gorm:"type:uuid;primaryKey;default:null;" json:"message_id,omitempty"` // MessageID is unique identifier for each message
	Message
	From      string    `json:"from,omitempty" gorm:"default:null"`
	CreatedAt time.Time `json:"created_at"`
}

ChatMessage is a Message but with additional metadata (for a multi-participant conversation)

func NewChatMessage

func NewChatMessage(from string, role string, text string) ChatMessage

NewChatMessage from (see also Message.ToChatMessage)

type ChatMessageFormatter

type ChatMessageFormatter = func(cm ChatMessage) ([]Message, bool)

ChatMessageFormatter may skip a message by returning false

The returned formatted messages are NEVER stored only sent

type ChatMessages

type ChatMessages []ChatMessage

ChatMessages like Messages but with additional metadata (for a multi-participant conversation)

func (ChatMessages) ToMessages

func (cm ChatMessages) ToMessages() Messages

func (ChatMessages) ToMessagesFormat

func (cm ChatMessages) ToMessagesFormat(fn ChatMessageFormatter) []Message

ToMessagesFormat returns a **temporary** convo for llm to produce a single new Message The returned convo should NEVER be stored (only sent to llm) as the Content is modified by formatter.

func (ChatMessages) Top added in v0.2.5

func (cm ChatMessages) Top() ChatMessage

func (ChatMessages) TopN added in v0.2.5

func (cm ChatMessages) TopN(n int) ChatMessages

func (ChatMessages) TopRole added in v0.2.5

func (cm ChatMessages) TopRole(role string) ChatMessage

type ChatRequest

type ChatRequest struct {
	Model     string           `json:"model,omitempty"`
	Messages  []Message        `json:"messages,omitempty"`
	Stream    bool             `json:"stream"`
	Think     *Think           `json:"reasoning_effort,omitempty"` // ollama endpoint "think" but /v1/chat/completions is "thinking"
	KeepAlive int              `json:"keep_alive,omitempty"`
	Tools     []json.Marshaler `json:"tools,omitempty"`
	*Options  `gorm:"type:jsonb;serializer:json;default:null"`
}

ChatRequest sent to LLM

type ChatResponse

type ChatResponse struct {
	LLMID string `json:"id,omitempty" gorm:"-"`
	//	Object string
	Model       string          `json:"model"`
	Created     json.RawMessage `json:"created,omitempty"`
	Done        bool            `json:"done"` // for stream
	DoneReason  string          `json:"done_reason,omitempty"`
	DoneReason2 string          `json:"finish_reason,omitempty" gorm:"-"` // INTERNAL do not use
	Message     Message         `json:"message"`                          // for stream, handlers
	Stats       `gorm:"embedded"`
	Choices     []Choice `json:"choices,omitempty"` // INTERNAL do not use
}

ChatResponse streamable has ChatResponse.Handler

Example:

var cr mllm.ChatResponse
err := <-mllm.Default().StreamBG(ctx, mllm.Messages{{Role: "user", Content: "hi"}}, false, cr.Handler)
println("Bot:", cr.Message.Content)

func (*ChatResponse) Handler

func (c *ChatResponse) Handler(in ChatResponse) error

Handler for E.StreamBG and E.Stream

Is a StreamHandler to gather Message

func (ChatResponse) SummaryString

func (c ChatResponse) SummaryString() string

SummaryString collects [ChatResponse.Stats] into a string

type Choice added in v0.3.1

type Choice struct {
	Index        int     `json:"index"`
	FinishReason *string `json:"finish_reason,omitempty"`
	Delta        Message `json:"delta"`
}

type E

type E struct {
	Host  url.URL // the LLM API (required)
	Model string  // the model name (required)

	Client  *http.Client      `json:"-"` // custom http client (optional)
	Headers map[string]string `json:"-"` // custom headers (optional)
	Tools   []json.Marshaler  // Bring your own tools (optional)
	Options *Options          // e options (optional)

	// Writer if non-nil will be automatically added as a stream handler to [[E.Get]] and [[E.Stream]]
	Writer    io.Writer `json:"-"`
	ThinkPre  string    // optional, default used in [NewEndpoint]
	ThinkPost string    // optional, default used in [NewEndpoint]

	// Handlers added to all requests
	Handlers []StreamHandler `json:"-"`
}

E (Endpoint) holds information for future requests

Writer for streaming output with ThinkPre and ThinkPost.

func NewEndpoint

func NewEndpoint(options *Options, tools []json.Marshaler) E

NewEndpoint reads env and returns a new reusable Endpoint

See E.StreamBG or E.Stream or E.Get

func (E) AddHandlers

func (e E) AddHandlers(h ...StreamHandler) E

func (E) Chat added in v0.2.5

func (e E) Chat(ctx context.Context, messages ChatMessages, think any) (ChatMessage, error)

func (E) Do added in v0.2.3

func (e E) Do(ctx context.Context, creq ChatRequest, handlers []StreamHandler) error

Do sends a ChatRequest and streams response in E.Read

func (E) Embed added in v0.2.2

func (e E) Embed(ctx context.Context, req EmbedRequest) (*EmbedResponse, error)

Embed generates new embeddings

See NewNomicDocument and NewNomicQuery for model-specific query prefixes

func (E) Get

func (e E) Get(ctx context.Context, messages []Message, think any) (ChatResponse, error)

Get calls E.Stream and waits until finished, returning the full ChatResponse

Uses E.Stream, writes stream text to [E.Writer] if non-nil, returns full ChatResponse

func (E) GetMessage added in v0.3.1

func (e E) GetMessage(ctx context.Context, messages []Message, think any) (Message, error)

func (E) NewRequest added in v0.2.3

func (e E) NewRequest(messages []Message, think any) *ChatRequest

NewRequest creates a ChatRequest adds e.Model, e.Tools, and e.Options

func (E) Read added in v0.2.2

func (e E) Read(reader *bufio.Reader, handlers ...StreamHandler) error

func (E) Stream

func (e E) Stream(ctx context.Context, messages []Message, think any, handlers ...StreamHandler) error

Stream with handlers(only) or to E.Writer, calls E.Do

func (E) StreamBG

func (e E) StreamBG(ctx context.Context, messages []Message, think any, handlers ...StreamHandler) chan error

StreamBG uses E.Stream, closing ch after streaming is finished, sending non-nil error to the channel

func (E) ValidateConfig

func (e E) ValidateConfig(think any) error

func (E) WithHandlers

func (e E) WithHandlers(h ...StreamHandler) E

func (E) WithOptions

func (e E) WithOptions(options *Options) E

func (E) WithTools

func (e E) WithTools(tools ...json.Marshaler) E

func (E) WithoutTools added in v0.2.1

func (e E) WithoutTools() E

type EmbedRequest added in v0.2.2

type EmbedRequest struct {
	Model     string `json:"model"`
	Input     string `json:"input"`
	KeepAlive int    `json:"keep_alive,omitempty"`
	// Dimensions truncates the output embedding to the specified dimension.
	Dimensions int   `json:"dimensions,omitempty"`
	Truncate   *bool `json:"truncate,omitempty"`
	// Options lists model-specific options.
	Options map[string]any `json:"options,omitempty"`

	Host url.URL `json:"-"` // optional: override E.Host and LLM_EMBED_HOST env
}

EmbedRequest is the request passed to E.Embed.

func NewEmbed added in v0.2.2

func NewEmbed(s string) EmbedRequest

NewEmbed

func NewNomicDocument added in v0.2.2

func NewNomicDocument(s string) EmbedRequest

NewNomicDocument adds `search_document: ` prefix to the given string

func NewNomicQuery added in v0.2.2

func NewNomicQuery(s string) EmbedRequest

NewNomicQuery adds `search_query: ` prefix to the given string

type EmbedResponse added in v0.2.2

type EmbedResponse struct {
	Model      string                `json:"model"`
	Embeddings []vector256.Vector256 `json:"embeddings"` // get resp.Embeddings[0]
	Data       []*struct {
		Embedding vector256.Vector256
	} `json:"data,omitempty"`
	Stats // limited for this endpoint
}

EmbedResponse is the response from [Client.Embed].

func (*EmbedResponse) GetEmbeddings added in v0.2.2

func (e *EmbedResponse) GetEmbeddings() vector256.Vector256

type ErrorWithResponse added in v0.2.2

type ErrorWithResponse struct {
	// contains filtered or unexported fields
}

func (ErrorWithResponse) Body added in v0.2.2

func (e ErrorWithResponse) Body() []byte

func (ErrorWithResponse) Unwrap added in v0.2.2

func (e ErrorWithResponse) Unwrap() error

Unwrap for errors.Unwrap.

type Message

type Message struct {
	Role       string            `json:"role,omitempty"`
	Content    string            `json:"content,omitempty"`
	Reasoning  string            `json:"thinking,omitempty" gorm:"default:null"`
	Reasoning2 string            `json:"reasoning,omitempty" gorm:"-"` // Internal, DO NOT USE
	Reasoning3 string            `json:"reasoning_content,omitempty" gorm:"-"`
	ToolCalls  []json.RawMessage `json:"tool_calls,omitempty" gorm:"type:jsonb;serializer:json;default:null;"`
	ToolName   string            `json:"tool_name,omitempty" gorm:"default:null"`
	ToolCallID string            `json:"tool_call_id,omitempty" gorm:"default:null"`
	Images     [][]byte          `json:"images,omitempty" gorm:"serializer:json;type:jsonb;default:null;"`
}

func (Message) Add

func (m Message) Add(m2 Message) Message

Add fields of c2 to c for gathering streamed chunks

Used in ChatResponse.Handler

func (Message) Equal

func (m Message) Equal(m2 Message) bool

func (Message) IsEmpty

func (m Message) IsEmpty() bool

func (Message) ToChatMessage

func (m Message) ToChatMessage(from string) ChatMessage

ToChatMessage with new UUID,from,timestamp. should be called immediately after stream is finished..

ChatMessage MAY be stored (NOT plain Message)

type Messages

type Messages []Message

func (Messages) Top added in v0.2.5

func (m Messages) Top() Message

func (Messages) TopN added in v0.2.5

func (m Messages) TopN(n int) Messages

func (Messages) TopRole added in v0.2.5

func (m Messages) TopRole(role string) Message

type Options

type Options struct {
	Seed             int64    `json:"seed,omitempty"`
	Temperature      *float64 `json:"temperature,omitempty"` // 0.0 to 2.0
	NumCtx           int      `json:"num_ctx,omitempty"`
	TopP             *float64 `json:"top_p,omitempty"`             // 0.0 to 1.0
	TopK             *int     `json:"top_k,omitempty"`             // 1 to ???
	FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"` // -2.0 to 2.0
	PresencePenalty  *float64 `json:"presence_penalty,omitempty"`  // -2.0 to 2.0
	//
	Debug bool `json:"-"` // internal, for printing full chatrequest before sending
}

Options for ChatRequest

type RawToolCall added in v0.3.1

type RawToolCall = Call[json.RawMessage]

type Stats

type Stats struct {
	LoadDuration       time.Duration `json:"load_duration,omitempty"`
	PromptEvalDuration time.Duration `json:"prompt_eval_duration,omitempty"`
	EvalDuration       time.Duration `json:"eval_duration,omitempty"`
	TotalDuration      time.Duration `json:"total_duration,omitempty"`
	PromptEvalCount    int           `json:"prompt_eval_count,omitempty"`
	EvalCount          int           `json:"eval_count,omitempty"`
	Usage
}

Stats has [Stats.String]

func (Stats) Summary added in v0.2.1

func (st Stats) Summary(w io.Writer) error

Summary in well-known format

func (Stats) SummaryString added in v0.2.1

func (st Stats) SummaryString() string

SummaryString collects [ChatResponse.Stats] into a string

type StreamHandler

type StreamHandler func(cr ChatResponse) error

func NewHandlerOut

func NewHandlerOut(w io.Writer, thinkpre string, thinkpost string) StreamHandler

NewHandlerOut plain 🍔

func (StreamHandler) HandleChat

func (c StreamHandler) HandleChat(cr ChatResponse) error

type Think

type Think struct {
	Value `json:"-"`
}

Think Value should be one of: nil,"low","medium","high",true,false

func (Think) MarshalJSON

func (t Think) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler for sending request

type Tool added in v0.3.1

type Tool struct {
	Spec json.RawMessage
	Exec any `json:"-"`
}

func (Tool) MarshalJSON added in v0.3.1

func (t Tool) MarshalJSON() ([]byte, error)

type Usage added in v0.3.1

type Usage struct {
	TotalTokens  int `json:"total_tokens,omitempty"`
	PromptTokens int `json:"prompt_tokens,omitempty"`
}

type Value added in v0.3.1

type Value = any

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL