Documentation
¶
Overview ¶
Package ai contains the AI SRE analyzer that turns Unknown / Spike AgentResults into structured AIFindings. The implementation is a plain net/http client against any OpenAI-compatible /chat/completions endpoint (vanilla OpenAI, Azure OpenAI, vLLM, LM Studio, Ollama, OpenRouter — anything that speaks the same JSON shape).
The system prompt is split across the prompts/ subdirectory in the OpenClaw / Versus DevOps Guidelines multi-file style — one file per concern (SOUL, INPUTS, OUTPUT, RULES). Each file is embedded at build time via go:embed and concatenated in a fixed order to form the `system` message. Operators tune the prompt by editing the Markdown files and rebuilding — no Go changes required.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BuildPrompt ¶
func BuildPrompt(r core.AgentResult, source, service string, samples []string) (system, user string)
BuildPrompt builds the (system, user) messages for a single AgentResult. Pure function: no I/O, no time. Easy to golden-test.
func ParseFinding ¶
ParseFinding accepts the model's reply and returns an AIFinding. Tolerates the most common deviations: leading/trailing prose, ```json fences, prepended "Here is the JSON:" preamble.
func SystemPrompt ¶
func SystemPrompt() string
SystemPrompt returns the assembled system prompt sent on every AI call. Exposed so the admin API can render it for operators.
Types ¶
type OpenAI ¶
type OpenAI struct {
// SampleFn extracts the sample lines passed to the model from an
// AgentResult. Defaults to "first up to 3 messages". Exposed so
// the worker can swap in a redaction-aware extractor without the
// AI package depending on pkg/agent.
SampleFn func(core.AgentResult) []string
// contains filtered or unexported fields
}
OpenAI is an AISRE backed by OpenAI's /chat/completions endpoint. One call per AgentResult; no streaming, no tool use.
func NewOpenAI ¶
func NewOpenAI(cfg config.AgentAIConfig, client *http.Client) *OpenAI
NewOpenAI constructs the analyzer. httpClient may be nil — a sane default (30s timeout, no proxy) is used.
func (*OpenAI) Analyze ¶
func (o *OpenAI) Analyze(ctx context.Context, r core.AgentResult) (*core.AICallResult, error)
Analyze sends the result to the configured chat endpoint and parses the structured AIFinding from the response. The returned AICallResult also carries the user prompt sent, the raw model response, the wall-clock duration, and the model id — used by the detect log to render an audit trail in the UI.
type RateLimiter ¶
type RateLimiter struct {
// contains filtered or unexported fields
}
RateLimiter caps the number of AI calls per rolling hour. Pure in-memory: a process restart resets the window, which is acceptable because the goal is to bound cost per running process — not to enforce a quota across a fleet (we run one agent worker per replica).
max <= 0 disables the limit (Allow always returns true).
func NewRateLimiter ¶
func NewRateLimiter(maxPerHour int) *RateLimiter
NewRateLimiter constructs a limiter with the configured per-hour cap.
func (*RateLimiter) Allow ¶
func (r *RateLimiter) Allow() bool
Allow returns true and records a use when the caller is under the per-hour cap, false otherwise. Thread-safe.
func (*RateLimiter) Stats ¶
func (r *RateLimiter) Stats() (bucket string, used, max int)
Stats returns the current hour bucket and how many calls have been allowed in it. Used by the admin status endpoint.
type ResultCache ¶
type ResultCache struct {
// contains filtered or unexported fields
}
ResultCache memoises AI findings by pattern_id so a recurring pattern inside one cache window does not pay for a repeat LLM call.
State is held in-memory and (best effort) mirrored to storage.Provider under config.AICacheBlobName so it survives restarts. A storage write failure logs but does not block the caller — the cache still works for the lifetime of the process.
func NewResultCache ¶
func NewResultCache(ttl time.Duration, store storage.Provider) *ResultCache
NewResultCache builds a cache with the configured TTL. ttl <= 0 disables caching (Get always misses, Put is a no-op).
store may be nil — in that case the cache is in-memory only.
func (*ResultCache) Flush ¶
func (c *ResultCache) Flush()
Flush wipes every entry and persists. Used by the admin endpoint.
func (*ResultCache) Get ¶
func (c *ResultCache) Get(patternID string) (*core.AIFinding, bool)
Get returns a cached finding when one exists and has not expired.
func (*ResultCache) Len ¶
func (c *ResultCache) Len() int
Len returns the number of live entries (including expired-but-not-evicted).
func (*ResultCache) Persist ¶
func (c *ResultCache) Persist() error
Persist writes the cache to storage. No-op when store is nil or the cache hasn't changed since the last persist.