77 lines
2.1 KiB
Go
77 lines
2.1 KiB
Go
// internal/brain/client.go
|
|
// Package brain provides a lightweight client for querying the ingestion server.
|
|
// Skill handlers call Query before spawning workers to inject relevant knowledge
|
|
// from the brain into the task prompt. Errors are suppressed — the brain is
|
|
// optional context; its absence must never block a skill invocation.
|
|
package brain
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
type queryResult struct {
|
|
Path string `json:"path"`
|
|
Title string `json:"title"`
|
|
Excerpt string `json:"excerpt"`
|
|
Score int `json:"score"`
|
|
}
|
|
|
|
// Query calls the ingestion server and returns relevant knowledge as a
|
|
// formatted string ready to prepend to a worker task prompt.
|
|
// Returns empty string (no error) when baseURL or query is empty,
|
|
// when the brain is unreachable, or when no results are found.
|
|
func Query(ctx context.Context, baseURL, query string, limit int) (string, error) {
|
|
if baseURL == "" || strings.TrimSpace(query) == "" {
|
|
return "", nil
|
|
}
|
|
if limit <= 0 {
|
|
limit = 3
|
|
}
|
|
|
|
body, _ := json.Marshal(map[string]any{"query": query, "limit": limit})
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+"/query", bytes.NewReader(body))
|
|
if err != nil {
|
|
slog.Warn("brain: build request failed", "err", err)
|
|
return "", nil
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
slog.Warn("brain: ingestion server unreachable", "err", err)
|
|
return "", nil
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
slog.Warn("brain: ingestion server returned non-OK", "status", resp.StatusCode)
|
|
return "", nil
|
|
}
|
|
|
|
out, _ := io.ReadAll(resp.Body)
|
|
var result struct {
|
|
Results []queryResult `json:"results"`
|
|
}
|
|
if err := json.Unmarshal(out, &result); err != nil || len(result.Results) == 0 {
|
|
return "", nil
|
|
}
|
|
|
|
var b strings.Builder
|
|
b.WriteString("## Relevant knowledge\n\n")
|
|
for _, r := range result.Results {
|
|
title := r.Title
|
|
if title == "" {
|
|
title = r.Path
|
|
}
|
|
fmt.Fprintf(&b, "### %s\n%s\n\n", title, r.Excerpt)
|
|
}
|
|
return b.String(), nil
|
|
}
|