137 lines
4.2 KiB
Go
137 lines
4.2 KiB
Go
// 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))]
|
||
}
|