Ports conversational_training.py to Go with InfluxDB reporting. 24 built-in seed conversations (Vi identity, philosophy, mindfulness). Supports extra JSONL files and golden set conversion to chat format. Also fixes InfluxDB client to accept 204 No Content on writes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
134 lines
3.4 KiB
Go
134 lines
3.4 KiB
Go
package lem
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// InfluxClient talks to an InfluxDB v3 instance.
|
|
type InfluxClient struct {
|
|
url string // e.g. "http://10.69.69.165:8181"
|
|
db string // e.g. "training"
|
|
token string // bearer token
|
|
}
|
|
|
|
// NewInfluxClient creates an InfluxClient for the given URL and database.
|
|
// Reads token from INFLUX_TOKEN env var first, then ~/.influx_token file.
|
|
// If url is empty, defaults to "http://10.69.69.165:8181".
|
|
// If db is empty, defaults to "training".
|
|
func NewInfluxClient(url, db string) *InfluxClient {
|
|
if url == "" {
|
|
url = "http://10.69.69.165:8181"
|
|
}
|
|
if db == "" {
|
|
db = "training"
|
|
}
|
|
|
|
token := os.Getenv("INFLUX_TOKEN")
|
|
if token == "" {
|
|
home, err := os.UserHomeDir()
|
|
if err == nil {
|
|
data, err := os.ReadFile(filepath.Join(home, ".influx_token"))
|
|
if err == nil {
|
|
token = strings.TrimSpace(string(data))
|
|
}
|
|
}
|
|
}
|
|
|
|
return &InfluxClient{
|
|
url: url,
|
|
db: db,
|
|
token: token,
|
|
}
|
|
}
|
|
|
|
// WriteLp writes line protocol data to InfluxDB.
|
|
// POST to /api/v3/write_lp?db={db} with lines joined by newlines.
|
|
func (c *InfluxClient) WriteLp(lines []string) error {
|
|
body := strings.Join(lines, "\n")
|
|
|
|
url := fmt.Sprintf("%s/api/v3/write_lp?db=%s", c.url, c.db)
|
|
|
|
req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(body))
|
|
if err != nil {
|
|
return fmt.Errorf("create write request: %w", err)
|
|
}
|
|
req.Header.Set("Authorization", "Bearer "+c.token)
|
|
req.Header.Set("Content-Type", "text/plain")
|
|
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("write request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
|
|
respBody, _ := io.ReadAll(resp.Body)
|
|
return fmt.Errorf("write failed %d: %s", resp.StatusCode, string(respBody))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// QuerySQL runs a SQL query against InfluxDB and returns the result rows.
|
|
// POST to /api/v3/query_sql with JSON body {"db": db, "q": sql}.
|
|
func (c *InfluxClient) QuerySQL(sql string) ([]map[string]interface{}, error) {
|
|
reqBody := map[string]string{
|
|
"db": c.db,
|
|
"q": sql,
|
|
}
|
|
|
|
jsonBody, err := json.Marshal(reqBody)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshal query request: %w", err)
|
|
}
|
|
|
|
url := c.url + "/api/v3/query_sql"
|
|
|
|
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(jsonBody))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create query request: %w", err)
|
|
}
|
|
req.Header.Set("Authorization", "Bearer "+c.token)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("query request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read query response: %w", err)
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("query failed %d: %s", resp.StatusCode, string(respBody))
|
|
}
|
|
|
|
var rows []map[string]interface{}
|
|
if err := json.Unmarshal(respBody, &rows); err != nil {
|
|
return nil, fmt.Errorf("unmarshal query response: %w", err)
|
|
}
|
|
|
|
return rows, nil
|
|
}
|
|
|
|
// escapeLp escapes spaces, commas, and equals signs for InfluxDB line protocol
|
|
// tag values.
|
|
func escapeLp(s string) string {
|
|
s = strings.ReplaceAll(s, `,`, `\,`)
|
|
s = strings.ReplaceAll(s, `=`, `\=`)
|
|
s = strings.ReplaceAll(s, ` `, `\ `)
|
|
return s
|
|
}
|