fix
This commit is contained in:
@@ -31,14 +31,15 @@ import (
|
||||
)
|
||||
|
||||
type server struct {
|
||||
apiKey string
|
||||
model string
|
||||
logReq atomic.Int64
|
||||
apiKey string
|
||||
model string
|
||||
categorizeConfidence float64
|
||||
logReq atomic.Int64
|
||||
}
|
||||
|
||||
type chatRequest struct {
|
||||
Model string `json:"model"`
|
||||
Messages []struct {
|
||||
Model string `json:"model"`
|
||||
Messages []struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
} `json:"messages"`
|
||||
@@ -50,11 +51,16 @@ func main() {
|
||||
addr := flag.String("addr", "127.0.0.1:18767", "listen address")
|
||||
key := flag.String("key", envOr("MOCK_LLM_API_KEY", "local-test"), "Bearer API key (non-empty placeholder)")
|
||||
model := flag.String("model", envOr("MOCK_LLM_MODEL", "mock-llm"), "model id returned by /v1/models")
|
||||
// Lets a local run reproduce a low-confidence taxonomy pick (see
|
||||
// processing.DefaultMinCategorizeConfidence).
|
||||
categorizeConfidence := flag.Float64("categorize-confidence", 0,
|
||||
"when > 0, answer categorize calls with this confidence score")
|
||||
flag.Parse()
|
||||
|
||||
s := &server{
|
||||
apiKey: strings.TrimSpace(*key),
|
||||
model: strings.TrimSpace(*model),
|
||||
apiKey: strings.TrimSpace(*key),
|
||||
model: strings.TrimSpace(*model),
|
||||
categorizeConfidence: *categorizeConfidence,
|
||||
}
|
||||
if s.apiKey == "" {
|
||||
log.Fatal("mock-llm: API key must be non-empty (Descrybe Completer.Enabled requires it)")
|
||||
@@ -77,6 +83,13 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func truncTrace(s string, n int) string {
|
||||
if len(s) <= n {
|
||||
return s
|
||||
}
|
||||
return s[:n]
|
||||
}
|
||||
|
||||
func envOr(k, def string) string {
|
||||
if v := strings.TrimSpace(os.Getenv(k)); v != "" {
|
||||
return v
|
||||
@@ -140,11 +153,17 @@ func (s *server) handleChatCompletions(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
system, user := splitMessages(req.Messages)
|
||||
if os.Getenv("MOCK_LLM_TRACE") != "" {
|
||||
log.Printf("mock-llm: roles=%d system_len=%d user_len=%d system_head=%q",
|
||||
len(req.Messages), len(system), len(user), truncTrace(system, 60))
|
||||
}
|
||||
var comp processing.Completion
|
||||
// A category formula in the prompt is answered in that formula's shape, so a
|
||||
// local run proves the formula reached the model and the reply passes the
|
||||
// pipeline's formula gate. Everything else keeps the heuristic fallback.
|
||||
if text, ok := buildFormulaReply(system, user); ok {
|
||||
if text, ok := s.categorizeReply(system, user); ok {
|
||||
comp = processing.Completion{Text: text}
|
||||
} else if text, ok := buildFormulaReply(system, user); ok {
|
||||
comp = processing.Completion{Text: text}
|
||||
} else {
|
||||
var err error
|
||||
@@ -204,6 +223,39 @@ func (s *server) handleEmbeddings(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// categorizeReply overrides the heuristic taxonomy pick so a local run can
|
||||
// reproduce a low-confidence answer.
|
||||
func (s *server) categorizeReply(system, user string) (string, bool) {
|
||||
if s.categorizeConfidence <= 0 || !strings.Contains(strings.ToLower(system), "categoryid") {
|
||||
return "", false
|
||||
}
|
||||
id := firstCategoryIDFromPrompt(user)
|
||||
if id == "" {
|
||||
return "", false
|
||||
}
|
||||
b, err := json.Marshal(map[string]any{"categoryId": id, "confidence": s.categorizeConfidence})
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
return string(b), true
|
||||
}
|
||||
|
||||
// firstCategoryIDFromPrompt mirrors the "(ID: …)" format of the categorize prompt.
|
||||
func firstCategoryIDFromPrompt(user string) string {
|
||||
const marker = "(id:"
|
||||
lower := strings.ToLower(user)
|
||||
at := strings.Index(lower, marker)
|
||||
if at < 0 {
|
||||
return ""
|
||||
}
|
||||
rest := strings.TrimSpace(user[at+len(marker):])
|
||||
end := strings.Index(rest, ")")
|
||||
if end <= 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(rest[:end])
|
||||
}
|
||||
|
||||
func splitMessages(msgs []struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
@@ -211,7 +263,10 @@ func splitMessages(msgs []struct {
|
||||
var users []string
|
||||
for _, m := range msgs {
|
||||
switch strings.ToLower(strings.TrimSpace(m.Role)) {
|
||||
case "system":
|
||||
// Reasoning-model requests send the system message as "developer"
|
||||
// (see processing.OpenAIClient sysRole). Treating it as a user turn made the
|
||||
// mock mis-route every call whose branch depends on the system prompt.
|
||||
case "system", "developer":
|
||||
if system == "" {
|
||||
system = m.Content
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user