Upload a recording (audio, or video with the camera off), get a private Swedish transcript, and chat about it. - ADR-033: RLS-scoped private transcript store; ffprobe/ffmpeg intake; gateway transcription (iguana kb-whisper, berget fallback); in-process single-replica worker; chat over the transcript (local models only). - ADR-034: optional two-engine mode (pianissimo + kb-whisper) reconciled by a LOCAL model; off unless TAPIR_RECONCILE_BASE_MODEL is set. - ADR-035: real progress (phases, running/quiet/lost), phase logging, every judged spot stored, merged segments. - Hardening from four adversarial reviews: errors logged and stored as kinds never text; per-engine deadlines; converted-length check; bounded queue; client hang-ups are not errors; strict local-alias check. - Dockerfile: static ffmpeg/ffprobe 7.0.2 pinned by sha256 (#34). Built and inspected by hand; CI does not build the image on a PR, so its first CI build is this merge. Uploads stay OFF until TAPIR_UPLOADS_ENABLED is set in the deployment. Closes #33, #34, #42, #43, #44. Co-authored-by: Mathias <mthbqv@gmail.com>
This commit was merged in pull request #39.
This commit is contained in:
@@ -25,12 +25,16 @@ import (
|
||||
// docs/homelab-integration.md.
|
||||
const defaultMaxTokens = 8192
|
||||
|
||||
// defaultTemperature is what every caller got before WithTemperature existed.
|
||||
const defaultTemperature = 0.2
|
||||
|
||||
// Client calls an OpenAI-compatible chat completions endpoint.
|
||||
type Client struct {
|
||||
baseURL string
|
||||
apiKey string
|
||||
model string
|
||||
maxTokens int
|
||||
temp float64
|
||||
httpClient *http.Client
|
||||
usageHook func(model string, prompt, completion int)
|
||||
}
|
||||
@@ -51,6 +55,35 @@ func WithMaxTokens(n int) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// maxErrorBody bounds how much of a failing response ends up in an error message.
|
||||
const maxErrorBody = 512
|
||||
|
||||
// HTTPError is a non-200 answer from the gateway. Its text keeps the form callers that
|
||||
// log it already rely on, with the body bounded; its Code is what code may branch on and
|
||||
// what errkind turns into a kind. The body is provider text and can echo the prompt, so
|
||||
// nothing private may persist or display Error() (ADR-035 review F3).
|
||||
type HTTPError struct {
|
||||
Code int
|
||||
Body string
|
||||
}
|
||||
|
||||
func (e *HTTPError) Error() string {
|
||||
body := e.Body
|
||||
if len(body) > maxErrorBody {
|
||||
body = body[:maxErrorBody] + "…"
|
||||
}
|
||||
return fmt.Sprintf("LLM returned %d: %s", e.Code, body)
|
||||
}
|
||||
|
||||
// HTTPStatus implements errkind.HTTPStatuser.
|
||||
func (e *HTTPError) HTTPStatus() int { return e.Code }
|
||||
|
||||
// WithTemperature overrides the sampling temperature. Zero IS a valid value and is sent
|
||||
// as zero: the reconciler's accuracy was measured at temperature 0 and must run there.
|
||||
func WithTemperature(t float64) Option {
|
||||
return func(c *Client) { c.temp = t }
|
||||
}
|
||||
|
||||
// WithUsageHook registers a callback fired after a successful completion with the
|
||||
// model and the prompt/completion token counts from the response usage block. It
|
||||
// keeps this copied, stdlib-only package (ADR-004) decoupled from metrics: the
|
||||
@@ -66,6 +99,7 @@ func New(baseURL, apiKey, model string, timeout time.Duration, opts ...Option) *
|
||||
apiKey: apiKey,
|
||||
model: model,
|
||||
maxTokens: defaultMaxTokens,
|
||||
temp: defaultTemperature,
|
||||
httpClient: &http.Client{Timeout: timeout},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
@@ -105,7 +139,7 @@ func (c *Client) Complete(ctx context.Context, system, user string) (string, err
|
||||
{Role: "system", Content: system},
|
||||
{Role: "user", Content: user},
|
||||
},
|
||||
Temperature: 0.2,
|
||||
Temperature: c.temp,
|
||||
MaxTokens: c.maxTokens,
|
||||
}
|
||||
b, err := json.Marshal(body)
|
||||
@@ -155,7 +189,7 @@ func (c *Client) Complete(ctx context.Context, system, user string) (string, err
|
||||
return "", fmt.Errorf("read response: %w", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("LLM returned %d: %s", resp.StatusCode, out)
|
||||
return "", &HTTPError{Code: resp.StatusCode, Body: string(out)}
|
||||
}
|
||||
|
||||
var cr chatResponse
|
||||
|
||||
Reference in New Issue
Block a user