同步
This commit is contained in:
1 parent
e67ff26e90
commit
58f23a2773
61 files changed
+11611
-443
No files matched your search
@@ -1,12 +1,16 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/Autumn-27/artex/db"
|
||||
"github.com/Autumn-27/artex/guard"
|
||||
"github.com/Autumn-27/artex/intercept"
|
||||
"github.com/Autumn-27/norma/llm"
|
||||
)
|
||||
|
||||
// chatGuard returns a guard wired with the manager's interceptor, used for chat
|
||||
@@ -15,6 +19,58 @@ func (s *Server) chatGuard() *guard.Guard {
|
||||
return guard.NewWithInterceptor(s.m.interceptor)
|
||||
}
|
||||
|
||||
// wireInterceptReviewer installs the LLM fallback judge into the interceptor. The
|
||||
// judge runs only on tool calls that matched no rule (see intercept.Judge). It
|
||||
// resolves the configured judge profile (0 → active/default), builds a provider,
|
||||
// runs a one-shot single-line classification, and parses ALLOW/ASK/DENY.
|
||||
func (s *Server) wireInterceptReviewer() {
|
||||
s.m.interceptor.SetReviewer(func(ctx context.Context, profileID int64, prompt, tool, command string) (intercept.Decision, error) {
|
||||
if profileID == 0 {
|
||||
if p, err := s.m.pg.ActiveProfile(); err == nil && p != nil {
|
||||
profileID = p.ID
|
||||
}
|
||||
}
|
||||
if profileID == 0 {
|
||||
return intercept.Decision{}, fmt.Errorf("未配置可用的裁判模型")
|
||||
}
|
||||
prov, _, ok := s.providerForProfile(profileID)
|
||||
if !ok {
|
||||
return intercept.Decision{}, fmt.Errorf("裁判模型 profile %d 不可用", profileID)
|
||||
}
|
||||
user := fmt.Sprintf("Tool: %s\nArguments:\n%s", tool, command)
|
||||
text, err := streamCollectText(ctx, prov, prompt, user)
|
||||
if err != nil {
|
||||
return intercept.Decision{}, err
|
||||
}
|
||||
v := intercept.ParseVerdict(text)
|
||||
return intercept.Decision{Action: v.Action, Message: v.Reason}, nil
|
||||
})
|
||||
}
|
||||
|
||||
// streamCollectText runs a single non-streaming-style completion (thinking off,
|
||||
// low temperature, tiny output) and returns the concatenated text. Used by the
|
||||
// LLM fallback judge, whose reply is one short line (ALLOW/ASK:.../DENY:...).
|
||||
func streamCollectText(ctx context.Context, prov llm.Provider, system, user string) (string, error) {
|
||||
temp := 0.0
|
||||
req := llm.CompletionRequest{
|
||||
System: []string{system},
|
||||
Messages: []llm.Message{llm.UserText(user)},
|
||||
MaxTokens: 128,
|
||||
Temperature: &temp,
|
||||
Thinking: "disabled",
|
||||
}
|
||||
var sb strings.Builder
|
||||
for ev, err := range prov.Stream(ctx, req) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if ev.Type == llm.SETextDelta {
|
||||
sb.WriteString(ev.Text)
|
||||
}
|
||||
}
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
// --- intercept rule CRUD ---
|
||||
|
||||
func (s *Server) interceptListRules(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -261,6 +317,40 @@ func (s *Server) interceptSetToolConfig(w http.ResponseWriter, r *http.Request)
|
||||
writeJSON(w, 200, map[string]any{"ok": true})
|
||||
}
|
||||
|
||||
// --- LLM fallback judge config (全局模型兜底) ---
|
||||
|
||||
// interceptGetJudgeConfig returns the resolved judge configuration. Prompt is the
|
||||
// effective prompt (built-in template when unset), so the UI can prefill it.
|
||||
func (s *Server) interceptGetJudgeConfig(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, 200, s.m.interceptor.GetJudgeConfig())
|
||||
}
|
||||
|
||||
// interceptSetJudgeConfig persists the judge configuration.
|
||||
func (s *Server) interceptSetJudgeConfig(w http.ResponseWriter, r *http.Request) {
|
||||
var req intercept.JudgeConfig
|
||||
if err := decode(r, &req); err != nil {
|
||||
writeErr(w, 400, err.Error())
|
||||
return
|
||||
}
|
||||
switch req.FailAction {
|
||||
case "allow", "ask", "deny":
|
||||
default:
|
||||
writeErr(w, 400, "fail_action 必须是 allow、ask 或 deny")
|
||||
return
|
||||
}
|
||||
switch req.AskTimeoutAction {
|
||||
case "allow", "deny":
|
||||
default:
|
||||
writeErr(w, 400, "ask_timeout_action 必须是 allow 或 deny")
|
||||
return
|
||||
}
|
||||
if err := s.m.interceptor.SetJudgeConfig(req); err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, map[string]any{"ok": true})
|
||||
}
|
||||
|
||||
// --- helpers ---
|
||||
|
||||
type interceptRuleReq struct {
|
||||
|
||||
Reference in new issue
Block a user