Documentation
¶
Overview ¶
Package assistant is the server half of the AI assistant built into the explorer (task #269): one conversation shape every provider speaks, the three drivers (OpenAI and every server that speaks its Chat Completions API, Anthropic's Messages API, Ollama), and the loop that runs a model's tool calls until it answers.
What the tools ARE is not this package's: the handlers hand the loop a Toolbox that runs filex's own MCP tools as the person who asked (handlers/assistant_tools.go). This package knows nothing about files, permissions or encryption - it moves words and tool calls between a model and that toolbox, within the budget an administrator set.
⚠ The provider's API key is held in memory by the driver and nowhere else here: it is never part of an error, a log line or an answer (redact).
Index ¶
- Constants
- Variables
- func ArgsFromText(s string) json.RawMessage
- func ArgsObject(a json.RawMessage) map[string]any
- func ArgsText(a json.RawMessage) string
- func DefaultBaseURL(k Kind) string
- func SuggestedModels(k Kind) []string
- func ValidKind(k Kind) bool
- type Batch
- type Decision
- type Invalid
- type Kind
- type Limiter
- type Loop
- type Message
- type Part
- type PartKind
- type Provider
- type ProviderError
- type Request
- type Result
- type Role
- type Settings
- type Stop
- type ToolCall
- type ToolDef
- type ToolOutcome
- type Toolbox
- type Turn
- type Usage
Constants ¶
const ( ReasonEnd = "end" ReasonPending = "pending" ReasonRounds = "rounds" ReasonBudget = "budget" ReasonMaxTokens = "max_tokens" ReasonRefusal = "refusal" ReasonOther = "other" )
Why a run ended, as Result.Reason says it.
const ( // StateOn: switched on and set up. StateOn = "on" // StateOff: switched off. StateOff = "off" // StateNotConfigured: switched on, but a provider, a model or a key is // missing - "the administrator has not set it up". StateNotConfigured = "not_configured" )
The assistant's states, as the explorer and the user settings say them.
Variables ¶
var ( BoundsToolRounds = [2]int{1, 50} BoundsOutputTokens = [2]int{256, 128000} BoundsRequestTokens = [2]int{2000, 10000000} BoundsTimeout = [2]int{10, 900} BoundsPerMinute = [2]int{1, 600} BoundsRetention = [2]int{1, 3650} )
Bounds of the numbers, each [min, max].
var ErrInvalid = errors.New("invalid assistant setting")
ErrInvalid wraps every refusal of Validate.
Functions ¶
func ArgsFromText ¶
func ArgsFromText(s string) json.RawMessage
ArgsFromText makes the arguments a model wrote storable: valid JSON as it is, `{}` for nothing, and anything else as a JSON string holding the text - a reply cut off mid-call, or a model that wrote broken JSON. The loop refuses arguments that are not an object (ArgsObject) and tells the model; they are kept so the conversation still says what it asked.
func ArgsObject ¶
func ArgsObject(a json.RawMessage) map[string]any
ArgsObject is a call's arguments when they are a JSON object; nil when they are anything else.
func ArgsText ¶
func ArgsText(a json.RawMessage) string
ArgsText is the text a model wrote for a call's arguments (ArgsFromText read back).
func DefaultBaseURL ¶
DefaultBaseURL is the address a provider answers on when the administrator names none.
func SuggestedModels ¶
SuggestedModels are the models the settings page offers before the provider has been asked for its list (Models). Only Anthropic's are known ahead: an OpenAI-compatible server or an Ollama install has whatever its operator put on it, so those come from the provider itself.
Types ¶
type Batch ¶
type Batch struct {
Calls []ToolCall `json:"calls"`
// Results are the answers so far, in the calls' order: Results[i]
// answers Calls[i]; the first call without one is the one waiting.
Results []Part `json:"results"`
}
Batch is the tool calls of one model turn, as far as they were answered: what a conversation keeps while a call waits for the person.
type Kind ¶
type Kind string
Kind names a provider.
const ( // KindOpenAI is OpenAI's Chat Completions API - and every server that // speaks it: OpenRouter, vLLM, LM Studio, a company gateway. KindOpenAI Kind = "openai" // KindAnthropic is Anthropic's Messages API (Claude). KindAnthropic Kind = "anthropic" // KindOllama is a local Ollama server's own chat API. KindOllama Kind = "ollama" )
type Limiter ¶
type Limiter struct {
// Now is the clock (time.Now when nil).
Now func() time.Time
// contains filtered or unexported fields
}
Limiter counts the requests each person started in the last minute.
type Loop ¶
type Loop struct {
Provider Provider
Model string
System string
Tools Toolbox
// MaxRounds is how many batches of tool calls one run may answer and go
// back to the model with.
MaxRounds int
// MaxOutputTokens is one reply's limit; Budget the whole run's, input and
// output of every call together.
MaxOutputTokens int
Budget int
// OnText receives the reply's text as it streams (nil: dropped).
OnText func(string)
// OnMessage receives every message the run appends, as it appends it -
// before the toolbox runs that message's calls (nil: none).
OnMessage func(Message)
}
Loop runs a conversation with a model and its tools, within a budget.
func (*Loop) Run ¶
Run goes on with the conversation history: answers resume's calls first when a batch was waiting (the waiting call carries the person's Decision), then asks the model until it answers, waits for the person, or reaches a limit. A provider failure ends the run with the error and what was done before it.
type Message ¶
Message is one turn of the conversation.
type Part ¶
type Part struct {
Kind PartKind `json:"kind"`
Text string `json:"text,omitempty"`
CallID string `json:"call_id,omitempty"`
Tool string `json:"tool,omitempty"`
Args json.RawMessage `json:"args,omitempty"`
IsError bool `json:"is_error,omitempty"`
Provider string `json:"provider,omitempty"`
Raw json.RawMessage `json:"raw,omitempty"`
// Context marks a text part the server wrote into the person's message:
// what they had selected (its paths in Args). Sent to the model like any
// text; the panel shows it as the selection, not as their words.
Context bool `json:"context,omitempty"`
}
Part is one piece of a message.
type PartKind ¶
type PartKind string
PartKind is what one piece of a message is.
const ( // PartText is words. PartText PartKind = "text" // PartToolCall is the model asking for a tool: CallID, Tool, Args. PartToolCall PartKind = "tool_call" // PartToolResult is a tool's answer to the call CallID: Text, IsError. PartToolResult PartKind = "tool_result" // PartOpaque is a block a provider asked to be sent back exactly as it // came (Anthropic's thinking blocks, with their signatures): Raw, and the // Provider it is for. Every other provider leaves it out. PartOpaque PartKind = "opaque" )
type Provider ¶
type Provider interface {
// Kind is the provider's name: KindOpenAI, KindAnthropic, KindOllama.
Kind() Kind
// Chat sends req and streams the reply: onText is called with every
// piece of text as it arrives (from this goroutine; never nil-checked by
// the caller - pass a no-op).
Chat(ctx context.Context, req Request, onText func(string)) (*Turn, error)
// Models lists the models the address offers, for the settings page.
Models(ctx context.Context) ([]string, error)
}
Provider is one model API.
type ProviderError ¶
type ProviderError struct {
Provider Kind
// Status is the HTTP status the provider answered; 0 when it never
// answered (the address did not answer, the stream broke).
Status int
// Message is the provider's own words (English, as it sent them).
Message string
}
ProviderError is a provider's refusal or failure, said without its key.
func (*ProviderError) Error ¶
func (e *ProviderError) Error() string
func (*ProviderError) Unauthorized ¶
func (e *ProviderError) Unauthorized() bool
Unauthorized reports whether the provider refused the key.
type Result ¶
type Result struct {
// Added are the messages the run appended to the conversation: the
// model's turns and the tool answers, in order.
Added []Message
// Pending is the batch that waits for the person (Reason "pending").
Pending *Batch
Reason string
Usage Usage
// Model is the model that answered last.
Model string
// RefusalCategory is the provider's reason for a refusal, when it gave
// one.
RefusalCategory string
}
Result is what one run of the loop did.
type Settings ¶
type Settings struct {
// Enabled turns the assistant on. Off by default: nothing leaves the
// server for a model until an administrator says so.
Enabled bool `json:"enabled"`
// HideButton keeps the assistant's button out of the explorer's toolbar
// for everybody, whatever else is set - the switch for an installation
// that wants no trace of it.
HideButton bool `json:"hide_button"`
Provider Kind `json:"provider"`
BaseURL string `json:"base_url"`
Model string `json:"model"`
// MaxToolRounds is how many times one request may go back to the model
// with tool answers before it is stopped.
MaxToolRounds int `json:"max_tool_rounds"`
// MaxOutputTokens is the longest single reply the model may write.
MaxOutputTokens int `json:"max_output_tokens"`
// MaxRequestTokens is one request's budget: input and output of every
// round together. Reaching it stops the request.
MaxRequestTokens int `json:"max_request_tokens"`
// TimeoutSeconds is how long one call to the provider may take, retries
// included.
TimeoutSeconds int `json:"timeout_seconds"`
// RequestsPerMinute is how many requests one person may start a minute.
RequestsPerMinute int `json:"requests_per_minute"`
// RetentionDays is how long a conversation is kept after it was last
// used.
RetentionDays int `json:"retention_days"`
}
Settings is what an administrator sets on Admin → Settings → AI assistant. The API key is not here: it is kept sealed beside it (handlers assistant_settings.go) and handed to New on its own.
func Defaults ¶
func Defaults() Settings
Defaults is a fresh install's settings: off, nothing chosen.
func (Settings) EffectiveBaseURL ¶
EffectiveBaseURL is the address the driver calls: the administrator's, or the provider's own.
func (Settings) NeedsKey ¶
NeedsKey reports whether this provider at this address cannot work without an API key: Anthropic always, OpenAI on OpenAI's own address. An OpenAI-compatible server of one's own (vLLM, LM Studio) and Ollama may run without one.
func (Settings) Normalized ¶
Normalized fills the numbers a stored document left out (a row written by an older version) with the defaults, and trims the strings.
type Stop ¶
type Stop string
Stop is why a model stopped.
const ( // StopEnd: it answered. StopEnd Stop = "end" // StopToolUse: it asked for tools and waits for their answers. StopToolUse Stop = "tool_use" // StopMaxTokens: the reply hit the output limit (MaxTokens). StopMaxTokens Stop = "max_tokens" // StopRefusal: the provider declined the request (a safety classifier, // a content filter). StopRefusal Stop = "refusal" // StopOther: anything the drivers do not map. StopOther Stop = "other" )
type ToolCall ¶
type ToolCall struct {
ID string `json:"id"`
Name string `json:"name"`
Args json.RawMessage `json:"args"`
Decision Decision `json:"decision,omitempty"`
}
ToolCall is one call the loop hands the toolbox.
type ToolDef ¶
type ToolDef struct {
Name string
Description string
Schema json.RawMessage
}
ToolDef is one tool as a model is told about it: its name, what it does and the JSON schema of its arguments.
type ToolOutcome ¶
type ToolOutcome struct {
// Text is what the model is told: the tool's answer, or why it did not
// run.
Text string
IsError bool
// Pending: the call waits for the person (it cannot be undone and they
// have not answered). The loop stops here; Text is not used.
Pending bool
}
ToolOutcome is what the toolbox made of a call.
type Toolbox ¶
type Toolbox interface {
// Defs are the tools the model is offered - the same list, in the same
// order, for every call of a conversation (a provider ties what it
// cached, and Anthropic its thinking blocks, to the tools).
Defs() []ToolDef
// Run runs one call (or decides it must wait for the person).
Run(ctx context.Context, call ToolCall) ToolOutcome
}
Toolbox runs the tools a model calls.
type Turn ¶
type Turn struct {
// Parts is the model's message: text, tool calls and the blocks its
// provider wants back (PartOpaque), in the order they came.
Parts []Part
Stop Stop
Usage Usage
// Model is the model that answered (a provider may name the exact
// version, or the model it fell back to).
Model string
// RefusalCategory is the provider's word for why it declined, when it
// said one.
RefusalCategory string
}
Turn is what one call to a model answered.