* fix(captain): restore inbox takeover and KB citations * fix(captain): harden grounded citations and smoke seed --------- Co-authored-by: Rogee <rogee@ipao.vip>
292 lines
9.1 KiB
Go
292 lines
9.1 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
listenAddr = ":8080"
|
|
maxRequestBody = 1 << 20
|
|
knowledgeHTML = `<!doctype html><html><head><title>GoChat Local Acceptance Knowledge</title></head><body><main><h1>Local acceptance policy</h1><p id="fixture-fact">The local fixture warranty window is 37 days. Reference marker: KBASE-LOCAL-2026.</p></main></body></html>`
|
|
)
|
|
|
|
type fixtureServer struct {
|
|
mu sync.Mutex
|
|
attempts map[[32]byte]int
|
|
}
|
|
|
|
type chatRequest struct {
|
|
Model string `json:"model"`
|
|
Messages []chatMessage `json:"messages"`
|
|
Tools []toolDefinition `json:"tools"`
|
|
Stream bool `json:"stream"`
|
|
}
|
|
|
|
type chatMessage struct {
|
|
Role string `json:"role"`
|
|
Content string `json:"content"`
|
|
Name string `json:"name"`
|
|
ToolCallID string `json:"tool_call_id"`
|
|
ToolCalls []toolCall `json:"tool_calls"`
|
|
}
|
|
|
|
type toolDefinition struct {
|
|
Function struct {
|
|
Name string `json:"name"`
|
|
} `json:"function"`
|
|
}
|
|
|
|
type toolCall struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Function struct {
|
|
Name string `json:"name"`
|
|
Arguments string `json:"arguments"`
|
|
} `json:"function"`
|
|
}
|
|
|
|
type embeddingRequest struct {
|
|
Model string `json:"model"`
|
|
Input []string `json:"input"`
|
|
}
|
|
|
|
func main() {
|
|
logger := log.New(os.Stdout, "captain-fixture ", log.LstdFlags)
|
|
server := &http.Server{Addr: listenAddr, Handler: newFixtureServer(logger), ReadHeaderTimeout: 5 * time.Second}
|
|
logger.Printf("listening addr=%s", listenAddr)
|
|
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
logger.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func newFixtureServer(logger *log.Logger) http.Handler {
|
|
s := &fixtureServer{attempts: make(map[[32]byte]int)}
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, _ *http.Request) {
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
|
})
|
|
mux.HandleFunc("GET /knowledge", func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
_, _ = io.WriteString(w, knowledgeHTML)
|
|
})
|
|
mux.HandleFunc("POST /v1/chat/completions", s.chat)
|
|
mux.HandleFunc("POST /v1/embeddings", s.embeddings)
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
status := &statusWriter{ResponseWriter: w, status: http.StatusOK}
|
|
mux.ServeHTTP(status, r)
|
|
logger.Printf("method=%s path=%s status=%d", r.Method, r.URL.Path, status.status)
|
|
})
|
|
}
|
|
|
|
func (s *fixtureServer) chat(w http.ResponseWriter, r *http.Request) {
|
|
body, ok := readBody(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
var req chatRequest
|
|
if err := json.Unmarshal(body, &req); err != nil || len(req.Messages) == 0 {
|
|
writeError(w, http.StatusBadRequest, "invalid_request")
|
|
return
|
|
}
|
|
latest := latestUserContent(req.Messages)
|
|
if strings.Contains(latest, "[fixture:fail]") {
|
|
writeError(w, http.StatusServiceUnavailable, "fixture_failure")
|
|
return
|
|
}
|
|
if strings.Contains(latest, "[fixture:retry]") && s.firstAttempt(body) {
|
|
writeError(w, http.StatusServiceUnavailable, "fixture_retry_once")
|
|
return
|
|
}
|
|
|
|
message, finishReason := fixtureChatMessage(req)
|
|
if req.Stream {
|
|
writeStream(w, req.Model, message.Content)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"id": "chatcmpl-fixture", "object": "chat.completion", "created": 1, "model": req.Model,
|
|
"choices": []map[string]any{{"index": 0, "message": message, "finish_reason": finishReason}},
|
|
"usage": map[string]int{"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
|
|
})
|
|
}
|
|
|
|
func (s *fixtureServer) embeddings(w http.ResponseWriter, r *http.Request) {
|
|
body, ok := readBody(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
var req embeddingRequest
|
|
if err := json.Unmarshal(body, &req); err != nil || len(req.Input) == 0 {
|
|
writeError(w, http.StatusBadRequest, "invalid_request")
|
|
return
|
|
}
|
|
content := strings.Join(req.Input, "\n")
|
|
if strings.Contains(content, "[fixture:embedding-fail]") {
|
|
writeError(w, http.StatusServiceUnavailable, "fixture_embedding_failure")
|
|
return
|
|
}
|
|
if strings.Contains(content, "[fixture:embedding-retry]") && s.firstAttempt(body) {
|
|
writeError(w, http.StatusServiceUnavailable, "fixture_embedding_retry_once")
|
|
return
|
|
}
|
|
vector := make([]float64, 1536)
|
|
for i := range vector {
|
|
vector[i] = 0.01
|
|
}
|
|
data := make([]map[string]any, len(req.Input))
|
|
for i := range req.Input {
|
|
data[i] = map[string]any{"object": "embedding", "index": i, "embedding": vector}
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"object": "list", "data": data, "model": req.Model,
|
|
"usage": map[string]int{"prompt_tokens": len(req.Input), "total_tokens": len(req.Input)},
|
|
})
|
|
}
|
|
|
|
func (s *fixtureServer) firstAttempt(body []byte) bool {
|
|
key := sha256.Sum256(body)
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.attempts[key]++
|
|
return s.attempts[key] == 1
|
|
}
|
|
|
|
func fixtureChatMessage(req chatRequest) (chatMessage, string) {
|
|
all := allMessageContent(req.Messages)
|
|
if strings.Contains(all, "Extract factual, self-contained FAQs") {
|
|
return chatMessage{Role: "assistant", Content: `{"faqs":[{"question":"What is the local fixture warranty window?","answer":"The local fixture warranty window is 37 days."}]}`}, "stop"
|
|
}
|
|
if last := req.Messages[len(req.Messages)-1]; last.Role == "tool" {
|
|
if last.Name == "activate_skill" {
|
|
if key := firstJSONListValue(last.Content, "reference_keys"); key != "" {
|
|
return toolMessage("fixture-read", "read_skill_reference", map[string]string{"skill_name": firstCatalogSkill(all), "reference_key": key}), "tool_calls"
|
|
}
|
|
}
|
|
return chatMessage{Role: "assistant", Content: "Fixture skill path completed."}, "stop"
|
|
}
|
|
if strings.Contains(latestUserContent(req.Messages), "[fixture:skill]") && hasTool(req.Tools, "activate_skill") {
|
|
if name := firstCatalogSkill(all); name != "" {
|
|
return toolMessage("fixture-activate", "activate_skill", map[string]string{"skill_name": name}), "tool_calls"
|
|
}
|
|
}
|
|
if strings.Contains(all, "Knowledge Base Context:") {
|
|
return chatMessage{Role: "assistant", Content: "The local fixture warranty window is 37 days [[1](http://captain-fixture:8080/knowledge)]."}, "stop"
|
|
}
|
|
return chatMessage{Role: "assistant", Content: "Fixture chat response."}, "stop"
|
|
}
|
|
|
|
func toolMessage(id, name string, args map[string]string) chatMessage {
|
|
raw, _ := json.Marshal(args)
|
|
call := toolCall{ID: id, Type: "function"}
|
|
call.Function.Name, call.Function.Arguments = name, string(raw)
|
|
return chatMessage{Role: "assistant", ToolCalls: []toolCall{call}}
|
|
}
|
|
|
|
func firstCatalogSkill(content string) string {
|
|
const start, end = "<available_skills_json>", "</available_skills_json>"
|
|
i := strings.Index(content, start)
|
|
if i < 0 {
|
|
return ""
|
|
}
|
|
j := strings.Index(content[i+len(start):], end)
|
|
if j < 0 {
|
|
return ""
|
|
}
|
|
var catalog []struct {
|
|
Name string `json:"name"`
|
|
}
|
|
if json.Unmarshal([]byte(content[i+len(start):i+len(start)+j]), &catalog) != nil || len(catalog) == 0 {
|
|
return ""
|
|
}
|
|
return catalog[0].Name
|
|
}
|
|
|
|
func firstJSONListValue(content, key string) string {
|
|
var payload map[string]any
|
|
start, end := strings.Index(content, "{"), strings.LastIndex(content, "}")
|
|
if start < 0 || end < start || json.Unmarshal([]byte(content[start:end+1]), &payload) != nil {
|
|
return ""
|
|
}
|
|
values, _ := payload[key].([]any)
|
|
if len(values) == 0 {
|
|
return ""
|
|
}
|
|
value, _ := values[0].(string)
|
|
return value
|
|
}
|
|
|
|
func hasTool(tools []toolDefinition, name string) bool {
|
|
for _, tool := range tools {
|
|
if tool.Function.Name == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func allMessageContent(messages []chatMessage) string {
|
|
var b strings.Builder
|
|
for _, message := range messages {
|
|
b.WriteString(message.Content)
|
|
b.WriteByte('\n')
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func latestUserContent(messages []chatMessage) string {
|
|
for i := len(messages) - 1; i >= 0; i-- {
|
|
if messages[i].Role == "user" {
|
|
return messages[i].Content
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func readBody(w http.ResponseWriter, r *http.Request) ([]byte, bool) {
|
|
body, err := io.ReadAll(io.LimitReader(r.Body, maxRequestBody+1))
|
|
if err != nil || len(body) == 0 || len(body) > maxRequestBody {
|
|
writeError(w, http.StatusBadRequest, "invalid_request")
|
|
return nil, false
|
|
}
|
|
return body, true
|
|
}
|
|
|
|
func writeStream(w http.ResponseWriter, model, content string) {
|
|
w.Header().Set("Content-Type", "text/event-stream")
|
|
chunk := map[string]any{
|
|
"id": "chatcmpl-fixture", "object": "chat.completion.chunk", "created": 1, "model": model,
|
|
"choices": []map[string]any{{"index": 0, "delta": map[string]string{"role": "assistant", "content": content}, "finish_reason": "stop"}},
|
|
}
|
|
raw, _ := json.Marshal(chunk)
|
|
_, _ = fmt.Fprintf(w, "data: %s\n\ndata: [DONE]\n\n", raw)
|
|
}
|
|
|
|
func writeError(w http.ResponseWriter, status int, code string) {
|
|
writeJSON(w, status, map[string]any{"error": map[string]string{"message": "local acceptance fixture error", "type": "fixture_error", "code": code}})
|
|
}
|
|
|
|
func writeJSON(w http.ResponseWriter, status int, value any) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
_ = json.NewEncoder(w).Encode(value)
|
|
}
|
|
|
|
type statusWriter struct {
|
|
http.ResponseWriter
|
|
status int
|
|
}
|
|
|
|
func (w *statusWriter) WriteHeader(status int) {
|
|
w.status = status
|
|
w.ResponseWriter.WriteHeader(status)
|
|
}
|