feat(admin): Agent 并发 - 后台可调同时对话上限并展示 Agent 服务状态
新增运行策略 agentMaxSessions(默认 30,范围 1-64),保存后同时下发到内嵌进程闸门与独立 yingce-agent 的 /v1/limit,降低上限不会中断已开始的对话。系统性能页展示 Agent 服务健康、进行中/排队数量,并可直接修改上限。
This commit is contained in:
@@ -14,6 +14,68 @@ import (
|
||||
agentruntime "infinite-canvas/backend/internal/agent/runtime"
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
OK bool `json:"ok"`
|
||||
Active int `json:"active"`
|
||||
Limit int `json:"limit"`
|
||||
Queued int `json:"queued"`
|
||||
}
|
||||
|
||||
func Inspect(ctx context.Context, endpoint string) (Status, error) {
|
||||
endpoint = strings.TrimRight(strings.TrimSpace(endpoint), "/")
|
||||
if endpoint == "" {
|
||||
return Status{}, fmt.Errorf("YINGCE_AGENT_URL is empty")
|
||||
}
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint+"/healthz", nil)
|
||||
if err != nil {
|
||||
return Status{}, err
|
||||
}
|
||||
response, err := remoteClient.Do(request)
|
||||
if err != nil {
|
||||
return Status{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return Status{}, fmt.Errorf("yingce-agent health returned HTTP %d", response.StatusCode)
|
||||
}
|
||||
var status Status
|
||||
if err := json.NewDecoder(io.LimitReader(response.Body, 4096)).Decode(&status); err != nil {
|
||||
return Status{}, err
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func SetLimit(ctx context.Context, endpoint, serviceToken string, limit int) error {
|
||||
endpoint = strings.TrimRight(strings.TrimSpace(endpoint), "/")
|
||||
serviceToken = strings.TrimSpace(serviceToken)
|
||||
if endpoint == "" {
|
||||
return fmt.Errorf("YINGCE_AGENT_URL is empty")
|
||||
}
|
||||
if len(serviceToken) < 32 {
|
||||
return fmt.Errorf("YINGCE_AGENT_TOKEN must contain at least 32 characters")
|
||||
}
|
||||
payload, err := json.Marshal(map[string]int{"maxSessions": limit})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodPut, endpoint+"/v1/limit", bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
request.Header.Set("Authorization", "Bearer "+serviceToken)
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
response, err := remoteClient.Do(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != http.StatusOK {
|
||||
detail, _ := io.ReadAll(io.LimitReader(response.Body, 1024))
|
||||
return fmt.Errorf("yingce-agent limit returned HTTP %d: %s", response.StatusCode, strings.TrimSpace(string(detail)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run executes one Agent session on the independent yingce-agent service.
|
||||
// The Go process keeps the bridge, billing, and tool execution. A configured
|
||||
// remote endpoint never falls back to an embedded Node process: doing so can
|
||||
|
||||
@@ -116,4 +116,14 @@ if (!response.ok) {
|
||||
if !called {
|
||||
t.Fatal("remote runtime did not call the Go tool bridge")
|
||||
}
|
||||
if err := SetLimit(ctx, endpoint, token, 16); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
status, err := Inspect(ctx, endpoint)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if status.Limit != 16 {
|
||||
t.Fatalf("limit = %d, want 16", status.Limit)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user