copilot

package
v0.9.692 Latest Latest
Warning

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

Go to latest
Published: Aug 5, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package copilot wraps an LLM Messages/Chat API to produce natural-language explanations of telemetry artifacts — trace flame, open Problem, exception group.

Three providers supported:

  • "anthropic": Anthropic Messages API (api.anthropic.com).
  • "github": GitHub Copilot Chat (api.githubcopilot.com). The caller's API key is a GitHub OAuth token (`ghu_…`) which we exchange for a short-lived Copilot session token (cached + auto-refreshed).
  • "openai": Any OpenAI-compatible /v1/chat/completions endpoint. Drives self-hosted local LLMs (Ollama, LM Studio, vLLM, llama.cpp server, LocalAI, OpenWebUI) AND the real OpenAI API. Banks running Coremetry air-gapped want this so traces / problems never leave the perimeter for explanation. APIKey is optional for local endpoints that don't gate on it (Ollama default).

The Service is configurable at runtime — admins can flip provider or rotate keys via the Settings UI without restarting Coremetry.

stream.go (v0.8.404) — token streaming for the one-shot narration call, with a TRANSPARENT runtime fallback to the buffered path.

StreamText is the streaming twin of Explain: same answer contract (full text + error return, one self-recorded ai_calls row), plus an onDelta callback fired per content chunk so the API layer can relay live tokens over its SSE stream. It covers the GUIDED chat path's single tool-less call — the clean streaming case.

Deliberately OUT of scope this slice:

  • The free tool loop (ChatWithTools) stays buffered. Tool-call streaming is a different beast: deltas interleave partial tool_call JSON fragments that must be reassembled per index before anything is executable, the answer text may arrive in multiple assistant turns, and provider extras (Gemini thought_signature, v0.8.373) ride on the reassembled call. None of that buys the operator visible latency wins — the loop's time goes to tool execution rounds, not narration.
  • GitHub Copilot: the session-token exchange + integration-header dance has no verified streaming contract; it uses the buffered call (zero deltas — the caller's final answer event still lands).

FALLBACK (the critical part — vLLM stream support is UNVERIFIED on the primary target, so the code adapts instead of assuming): when the stream:true request fails at CONNECT/first-byte — non-200, non-SSE content-type, immediate EOF before any event, JSON error body — we transparently retry ONCE with the existing buffered call and log "copilot stream unsupported, buffered fallback". A deterministic rejection additionally caches an "unsupported" verdict per (provider,baseURL,model) so subsequent guided calls skip the probe; Configure resets the cache. Mid-stream failures (after data has flowed) do NOT fall back — deltas already reached the client.

Index

Constants

View Source
const (
	ProviderAnthropic = "anthropic"
	ProviderGitHub    = "github"
	ProviderOpenAI    = "openai"
)
View Source
const AnswerInTurkish = "\n\nHer zaman Türkçe yanıt ver."

AnswerInTurkish is appended to every PROSE copilot surface (v0.8.374, operator decision: "hepsi Türkçe" — the AI-analysis panel was already Turkish while Explain answered in English). Strict-JSON surfaces (systemNLToQuery, systemCHQueryOptimize, systemServiceTags) deliberately do NOT get it: a language directive invites prose around machine-parsed output. Exported so the api package's chat prompt shares the exact same line. Pinned by TestProsePromptsAnswerInTurkish.

Variables

This section is empty.

Functions

func SystemPromptAnomaly added in v0.5.23

func SystemPromptAnomaly() string

func SystemPromptCHQueryOptimize added in v0.6.8

func SystemPromptCHQueryOptimize() string

func SystemPromptCompareTraces added in v0.5.74

func SystemPromptCompareTraces() string

func SystemPromptDeployImpact added in v0.5.78

func SystemPromptDeployImpact() string

func SystemPromptException

func SystemPromptException() string

func SystemPromptIncident added in v0.5.23

func SystemPromptIncident() string

func SystemPromptNLToQuery added in v0.5.255

func SystemPromptNLToQuery() string

func SystemPromptProblem

func SystemPromptProblem() string

func SystemPromptRCAVerdict added in v0.9.559

func SystemPromptRCAVerdict() string

SystemPromptRCAVerdict — hakem prompt'u (v0.9.559).

func SystemPromptRootCauseNarration added in v0.8.170

func SystemPromptRootCauseNarration() string

func SystemPromptRunbook added in v0.5.35

func SystemPromptRunbook() string

func SystemPromptSLOBurn added in v0.5.82

func SystemPromptSLOBurn() string

func SystemPromptServiceHealth added in v0.5.31

func SystemPromptServiceHealth() string

func SystemPromptServiceTags added in v0.5.49

func SystemPromptServiceTags() string

func SystemPromptSlowQuery added in v0.5.171

func SystemPromptSlowQuery() string

func SystemPromptSpan added in v0.5.144

func SystemPromptSpan() string

func SystemPromptTrace

func SystemPromptTrace() string

func WithJSONMode added in v0.9.517

func WithJSONMode(ctx context.Context) context.Context

WithJSONMode — modelden KATI JSON isteyen çağrılar için.

response_format sunucu tarafında çözümlemeyi kısıtlar: model JSON DIŞINA çıkamaz. İyi bir modelde bile değerli — JSON kaçakları nadir ama sessiz, ve bu yüzeyler post-check ile temizlemeye çalışıyordu. Kısıt o sınıfı tamamen kapatıyor. Desteklemeyen uçta sessizce eski davranışa düşer (bir kez yoklanır, karar önbelleklenir).

func WithJSONSchema added in v0.9.527

func WithJSONSchema(ctx context.Context, name string, schema map[string]any) context.Context

WithJSONSchema — çıktının ŞEKLİNİ de dayatan üst basamak (v0.9.527).

`json_object` yalnız "geçerli JSON" der; alan eksik olabilir, tip kayabilir, enum dışı değer gelebilir. `json_schema` çözümlemeyi şemaya kilitler — asıl kazanç ENUM: bugün sunucu tarafında elle temizlenen alanlar (filtre operatörü, aralık ön-ayarı, güven seviyesi) modelin üretebileceği küme dışına çıkar.

ÖNEMLİ: sunucu-tarafı doğrulama bunun yerine GEÇMEZ. Şema desteği yoklamayla kapanmış olabilir, basamak düşmüş olabilir, uç eski olabilir — üç durumda da yanıt şemasız gelir. Şema kaliteyi yükseltir, doğrulamanın yerini almaz.

Şema OpenAI `strict` kurallarına uygun olmalı: her object'te `additionalProperties: false` ve tüm anahtarlar `required`.

func WithMeta added in v0.5.164

func WithMeta(ctx context.Context, m CallMeta) context.Context

WithMeta returns ctx tagged with the given CallMeta. The api package's copilotExplain wrapper uses this to attribute every LLM call to the surface that produced it.

Types

type CallMeta added in v0.5.164

type CallMeta struct {
	Surface   string
	UserID    string
	UserEmail string
	// ExchangeID — see CallRecord.ExchangeID (v0.8.399). Carried in
	// ctx so both the RecordUsage path (free chat loop) and the
	// Explain self-recording path (guided chat) stamp the same id
	// without new parameters on either call chain.
	ExchangeID string
}

CallMeta is attribution data the API layer stashes in ctx before calling Explain — surface (which Copilot endpoint), userID/email for "who triggered this call" filtering on the /ai page.

func MetaFromContext added in v0.5.164

func MetaFromContext(ctx context.Context) CallMeta

MetaFromContext is the read side. Returns the zero CallMeta when no tag is present so callers can treat it as "unknown".

type CallRecord added in v0.5.164

type CallRecord struct {
	CreatedAt time.Time
	Surface   string
	// ExchangeID (v0.8.399) — correlation key for operator feedback:
	// the chat handler mints one id per exchange, emits it to the UI
	// in the SSE answer event, and threads it here via CallMeta so a
	// thumbs up/down (ai_feedback row) can be joined back to the
	// ai_calls row it rates. Empty for surfaces that don't emit one.
	// Provider-agnostic — pure correlation plumbing, no LLM coupling.
	ExchangeID     string
	Provider       string
	Model          string
	BaseURL        string
	DurationMs     uint32
	InputTokens    uint32
	OutputTokens   uint32
	Status         string
	ErrorMsg       string
	PromptChars    uint32
	ResponseChars  uint32
	UserID         string
	UserEmail      string
	PromptSample   string
	ResponseSample string
}

CallRecord captures one LLM round-trip. CreatedAt is set by the Explain wrapper at call start; DurationMs measured at return. Token counts come from the provider response when available (OpenAI + Anthropic both ship usage data; some Ollama versions don't — those stay 0).

type ChatMessage added in v0.6.53

type ChatMessage struct {
	Role        string       // "user" | "assistant"
	Text        string       `json:",omitempty"`
	ToolCalls   []ToolCall   `json:",omitempty"`
	ToolResults []ToolResult `json:",omitempty"`
}

ChatMessage is one provider-neutral conversation turn. A user turn carries Text (the question) OR ToolResults (function outputs fed back). An assistant turn carries Text (prose) and/or ToolCalls (functions it wants run).

type ChatTurn added in v0.6.53

type ChatTurn struct {
	Text         string
	ToolCalls    []ToolCall
	InputTokens  uint32
	OutputTokens uint32
}

ChatTurn is one model response. When ToolCalls is non-empty the caller must execute them and loop; otherwise Text is the final answer.

type Recorder added in v0.5.164

type Recorder interface {
	RecordCall(ctx context.Context, c CallRecord)
}

Recorder is the sink for the Coremetry-native AI observability pipeline. Implemented by a thin adapter around chstore.Store (kept in package api to avoid copilot→chstore import dependency). Every Explain call emits exactly one CallRecord regardless of success — errors show up in /ai with status="error" so the operator sees broken provider configs without grepping logs.

type Service

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

Service is the small surface other packages call into.

Internals are guarded by mu so PUT /api/settings/ai can swap creds while Explain calls are in flight.

func New

func New(provider, apiKey, model string) *Service

New always returns a Service. When apiKey is empty Configured() reports false and callers branch off — that's the dormant state before the operator pastes a key in Settings.

func (*Service) Active added in v0.8.189

func (s *Service) Active() bool

Active reports whether the Copilot is BOTH enabled AND configured. This is the gate for any path that actually calls the provider — the background ProblemExplainer, Explain/ChatWithTools, the AI-usage HTTP endpoints, and the UI feature-flag (/api/copilot/config).

Distinct from Configured(): when the operator flips "Enable AI Copilot" OFF in Settings we KEEP the stored creds (Configured() stays true so the Settings form still renders) but Active() goes false — the background explainer stops hammering the provider, the AI affordances hide, and the AI endpoints 503. Re-enabling is one click (no key to re-paste). wf.

Inlines Configured()'s logic under a single RLock so the enabled gate and the cred check read a consistent snapshot.

func (*Service) ChatWithTools added in v0.6.53

func (s *Service) ChatWithTools(ctx context.Context, system string, msgs []ChatMessage, tools []ToolSpec) (ChatTurn, error)

ChatWithTools runs ONE model turn over the conversation with the given tools available. Branches on the configured provider. No ai_calls recording here — the handler records once per user message after the agentic loop settles (RecordUsage), summing the per-turn token usage, so one chat exchange = one ai_calls row.

func (*Service) Configure

func (s *Service) Configure(provider, apiKey, model, baseURL string, skipTLS, enabled bool)

Configure swaps live credentials. Used by PUT /api/settings/ai. Empty apiKey legitimately disables the feature — Configured() flips to false and the UI hides the buttons. baseURL is only consulted by the "openai" provider; ignored for anthropic/github so a stale value persisted from a previous selection doesn't leak. v0.5.360: skipTLS rebuilds the http.Client transport when it flips; otherwise the existing client is kept (its 180s timeout matches the local-LLM use case). wf: enabled is the master on/off switch — set here so it lives behind the same lock as the creds and can't tear with an in-flight Active()/Explain.

func (*Service) Configured

func (s *Service) Configured() bool

Configured reports whether the service has credentials. The "openai" provider with an empty key is allowed when baseURL points at a local endpoint that doesn't gate on auth (Ollama default config) — the caller's request just goes through with no Authorization header.

func (*Service) Explain

func (s *Service) Explain(ctx context.Context, systemPrompt, userPrompt string) (string, error)

Explain runs a single Messages/Chat call with the given system + user prompt. Branches on the configured provider. v0.5.162 wraps the dispatch with the AI-observability recorder so every call emits an ai_calls row regardless of success — recording happens on a goroutine so the user doesn't pay ingest cost in their request path.

func (*Service) LoadPersisted

func (s *Service) LoadPersisted(ctx context.Context, store SettingsStore) error

LoadPersisted reads any DB-saved override and applies it. Silently skips when nothing's saved — env defaults stay in effect.

func (*Service) QuotaBackoffActive added in v0.9.200

func (s *Service) QuotaBackoffActive() bool

QuotaBackoffActive reports whether the quota circuit-breaker window is open. Background consumers (problem-explainer) skip their tick while true; interactive surfaces ignore it.

func (*Service) RecordUsage added in v0.6.53

func (s *Service) RecordUsage(ctx context.Context, inTok, outTok uint32, status, errMsg, promptSample, respSample string)

RecordUsage writes a single ai_calls row for a completed chat exchange. Mirrors the recording block in Explain so the /ai page attributes chat usage alongside the ✨ Explain surfaces. Surface comes from MetaFromContext (the handler sets it to "chat").

func (*Service) SavePersisted

func (s *Service) SavePersisted(ctx context.Context, store SettingsStore, provider, apiKey, model, baseURL string, skipTLS, enabled bool) error

SavePersisted writes new credentials to system_settings AND updates the live Service. Called by PUT /api/settings/ai. v0.5.360 — skipTLS plumbed through end-to-end. wf — enabled persisted as a pointer (&enabled) so the round-trip is explicit; once SavePersisted has run the blob always carries the field. The disable-without-clearing-creds path is just enabled=false with the apiKey left untouched.

func (*Service) SetRecorder added in v0.5.164

func (s *Service) SetRecorder(r Recorder)

SetRecorder wires the observability sink. Nil disables it. Safe to call before the Service is in use (single goroutine at boot).

func (*Service) Snapshot

func (s *Service) Snapshot() (provider, model, baseURL string, hasKey, skipTLS, enabled bool)

Snapshot returns the current configuration. The apiKey is masked (only "set" / "unset" matters to the UI) — full key is never echoed. baseURL is non-secret (operators put it in their Helm values), so we echo it back so the Settings page can show what's wired up. v0.5.360 — skipTLS surfaced so the UI checkbox reflects what's actually live. wf — enabled surfaced so getAISettings can drive the Settings toggle independently of whether a key is stored.

func (*Service) StartConfigRefresh added in v0.5.324

func (s *Service) StartConfigRefresh(ctx context.Context, store SettingsStore, interval time.Duration)

StartConfigRefresh — v0.5.324. Background poll: keeps the in-memory Copilot config in sync with the shared persisted blob across pods. interval ≤ 0 → 30s.

func (*Service) StreamText added in v0.8.404

func (s *Service) StreamText(ctx context.Context, systemPrompt, userPrompt string, onDelta func(string)) (string, error)

StreamText runs a single system+user narration call, streaming answer tokens through onDelta as they arrive. Returns the FULL final text — identical to what Explain would have returned — so the caller keeps its existing "answer is the source of truth" contract; the deltas are a pure progressive-rendering bonus. onDelta may be nil. Reasoning output (delta.reasoning_content / delta.reasoning / inline <think> blocks / Anthropic thinking_delta) is buffered silently and never streamed; if the model emits ONLY reasoning, the salvaged answer (v0.8.384 chain) is emitted as one final delta.

type SettingsStore

type SettingsStore interface {
	GetSetting(ctx context.Context, key string) ([]byte, error)
	PutSetting(ctx context.Context, key string, value []byte) error
}

SettingsStore is the small slice of *chstore.Store we need — declared as an interface here so this package doesn't import chstore (which would cycle through callers).

type ToolCall added in v0.6.53

type ToolCall struct {
	ID    string          // provider-issued id, echoed back in the result
	Name  string          // tool name
	Input json.RawMessage // arguments as JSON
	// Raw is the COMPLETE tool_call object exactly as the openai-compat
	// provider returned it (v0.8.373, operator-reported). Gemini's
	// compat endpoint attaches extra fields (extra_content →
	// thought_signature) and REJECTS the next turn with 400
	// INVALID_ARGUMENT when the replayed functionCall lacks them —
	// rebuilding the object from the trimmed fields above silently
	// dropped everything unknown. When set, the replay encoder sends
	// Raw verbatim; ID/Name/Input remain the parsed view for the
	// executor. Nil for the Anthropic path and legacy messages.
	Raw json.RawMessage `json:",omitempty"`
}

ToolCall is one function invocation the model requested.

type ToolResult added in v0.6.53

type ToolResult struct {
	CallID  string
	Name    string
	Content string // JSON-stringified tool output (or an error string)
	IsError bool
}

ToolResult is the output of executing a ToolCall, fed back into the next turn so the model can read it.

type ToolSpec added in v0.6.53

type ToolSpec struct {
	Name        string
	Description string
	InputSchema map[string]any
}

ToolSpec is the provider-neutral function description handed to the LLM. InputSchema is JSON Schema (draft 2020-12) — the same shape the MCP tools already declare, so the API handler maps mcp.Tool → ToolSpec 1:1.

Jump to

Keyboard shortcuts

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