textmachine/eval/door_to_file/stub/main.go

137 lines
4.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Command stub serves the deployment's zero-cost pair over the local provider's address, so the
// whole door-to-file chain can be exercised before a cent is spent. It is the stand's own stub —
// the P9 live-probe's fake_provider.py as the platform's live tests carry it
// (platform/internal/runner/translate_resnapshot_live_test.go, fakeProvider/proseFor) — lifted out
// of the test binary so a driver can raise it, with one addition the tests do not need: every call
// is appended to a JSONL log, which is the only trace that says how many calls the chain really
// made and with which role.
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"net"
"net/http"
"os"
"regexp"
"strings"
"sync"
"time"
)
var (
addr = flag.String("addr", "127.0.0.1:11434", "address of the local provider")
logPath = flag.String("log", "", "append one JSON line per call here")
)
var (
mu sync.Mutex
calls int
)
func main() {
flag.Parse()
ln, err := net.Listen("tcp", *addr)
if err != nil {
log.Fatalf("the local provider address is busy (another stand?): %v", err)
}
fmt.Printf("stub listening on %s pid=%d\n", *addr, os.Getpid())
srv := &http.Server{Handler: http.HandlerFunc(serve)}
log.Fatal(srv.Serve(ln))
}
func serve(w http.ResponseWriter, r *http.Request) {
var req struct {
Model string `json:"model"`
Messages []struct {
Role string `json:"role"`
Content string `json:"content"`
} `json:"messages"`
}
_ = json.NewDecoder(r.Body).Decode(&req)
var system, user strings.Builder
for _, m := range req.Messages {
switch m.Role {
case "system":
system.WriteString(m.Content)
case "user":
user.WriteString(m.Content)
}
}
content := proseFor(system.String(), user.String())
mu.Lock()
calls++
n := calls
mu.Unlock()
record(map[string]any{
"at": time.Now().UTC().Format(time.RFC3339Nano), "seq": n, "path": r.URL.Path,
"model": req.Model, "role": roleOf(system.String()),
"system_bytes": system.Len(), "user_bytes": user.Len(), "out_bytes": len(content),
})
resp := map[string]any{
"id": "stub-" + fmt.Sprint(n), "object": "chat.completion", "model": req.Model,
"choices": []map[string]any{{
"index": 0,
"message": map[string]any{"role": "assistant", "content": content},
"finish_reason": "stop",
}},
"usage": map[string]any{"prompt_tokens": 100, "completion_tokens": max(1, len(content)/3),
"total_tokens": 100 + max(1, len(content)/3)},
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(resp)
}
func record(row map[string]any) {
if *logPath == "" {
return
}
f, err := os.OpenFile(*logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return
}
defer f.Close()
line, _ := json.Marshal(row)
_, _ = f.Write(append(line, '\n'))
}
// roleOf names which of the chain's roles asked, by the same marks proseFor answers to.
func roleOf(system string) string {
switch {
case strings.Contains(system, "терминолог"):
return "terminologist"
case strings.Contains(system, "классиф") || strings.Contains(system, "класс (name | place | title | term)"):
return "classifier"
default:
return "prose"
}
}
var termKeys = regexp.MustCompile(`(?m)^key: (.+)$`)
func proseFor(system, user string) string {
if strings.Contains(system, "терминолог") {
var b strings.Builder
for i, m := range termKeys.FindAllStringSubmatch(user, -1) {
fmt.Fprintf(&b, "%s\tЗаглушка-%d\t55\n", m[1], i+1)
}
return b.String()
}
if strings.Contains(system, "классиф") || strings.Contains(system, "класс (name | place | title | term)") {
var b strings.Builder
for _, m := range termKeys.FindAllStringSubmatch(user, -1) {
fmt.Fprintf(&b, "%s\tname\n", m[1])
}
return b.String()
}
const filler = "Фан Юань неторопливо шёл по горной тропе, и ветер приносил запах трав. " +
"Старейшина посмотрел на него и тяжело вздохнул, вспоминая давние годы. " +
"В долине клубился туман, и где-то вдалеке кричала ночная птица. "
out := filler
for len(out) < len(user) {
out += filler
}
return out[:max(len(user), len(filler))]
}