feat(admin): Agent 并发 - 后台可调同时对话上限并展示 Agent 服务状态
新增运行策略 agentMaxSessions(默认 30,范围 1-64),保存后同时下发到内嵌进程闸门与独立 yingce-agent 的 /v1/limit,降低上限不会中断已开始的对话。系统性能页展示 Agent 服务健康、进行中/排队数量,并可直接修改上限。
This commit is contained in:
@@ -222,27 +222,83 @@ func RuntimeDir() (string, error) {
|
||||
return "", errors.New("Agent runtime files are missing; set CANVAS_PI_RUNTIME_DIR")
|
||||
}
|
||||
|
||||
var (
|
||||
agentSlotsOnce sync.Once
|
||||
agentSlots chan struct{}
|
||||
const (
|
||||
defaultAgentProcessLimit = 30
|
||||
maxAgentProcessLimit = 64
|
||||
)
|
||||
|
||||
func acquireAgentProcess(ctx context.Context) (func(), error) {
|
||||
agentSlotsOnce.Do(func() {
|
||||
limit := 8
|
||||
if raw := strings.TrimSpace(os.Getenv("CANVAS_AGENT_MAX_PROCESSES")); raw != "" {
|
||||
if parsed, err := strconv.Atoi(raw); err == nil && parsed > 0 && parsed <= 1000 {
|
||||
limit = parsed
|
||||
}
|
||||
type agentProcessGate struct {
|
||||
mu sync.Mutex
|
||||
limit int
|
||||
active int
|
||||
wake chan struct{}
|
||||
}
|
||||
|
||||
var processGate = newAgentProcessGate()
|
||||
|
||||
func newAgentProcessGate() *agentProcessGate {
|
||||
limit := defaultAgentProcessLimit
|
||||
if raw := strings.TrimSpace(os.Getenv("CANVAS_AGENT_MAX_PROCESSES")); raw != "" {
|
||||
if parsed, err := strconv.Atoi(raw); err == nil {
|
||||
limit = clampAgentProcessLimit(parsed)
|
||||
}
|
||||
agentSlots = make(chan struct{}, limit)
|
||||
})
|
||||
select {
|
||||
case agentSlots <- struct{}{}:
|
||||
return func() { <-agentSlots }, nil
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
return &agentProcessGate{limit: limit, wake: make(chan struct{})}
|
||||
}
|
||||
|
||||
func clampAgentProcessLimit(value int) int {
|
||||
if value < 1 {
|
||||
return defaultAgentProcessLimit
|
||||
}
|
||||
if value > maxAgentProcessLimit {
|
||||
return maxAgentProcessLimit
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// SetProcessLimit changes how many embedded Agent processes may run at once.
|
||||
// Lowering the limit does not stop a process that has already started.
|
||||
func SetProcessLimit(value int) (active int, limit int) {
|
||||
processGate.mu.Lock()
|
||||
defer processGate.mu.Unlock()
|
||||
processGate.limit = clampAgentProcessLimit(value)
|
||||
close(processGate.wake)
|
||||
processGate.wake = make(chan struct{})
|
||||
return processGate.active, processGate.limit
|
||||
}
|
||||
|
||||
func ProcessUsage() (active int, limit int) {
|
||||
processGate.mu.Lock()
|
||||
defer processGate.mu.Unlock()
|
||||
return processGate.active, processGate.limit
|
||||
}
|
||||
|
||||
func acquireAgentProcess(ctx context.Context) (func(), error) {
|
||||
for {
|
||||
processGate.mu.Lock()
|
||||
if processGate.active < processGate.limit {
|
||||
processGate.active++
|
||||
processGate.mu.Unlock()
|
||||
return releaseAgentProcess, nil
|
||||
}
|
||||
wake := processGate.wake
|
||||
processGate.mu.Unlock()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-wake:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func releaseAgentProcess() {
|
||||
processGate.mu.Lock()
|
||||
if processGate.active > 0 {
|
||||
processGate.active--
|
||||
}
|
||||
close(processGate.wake)
|
||||
processGate.wake = make(chan struct{})
|
||||
processGate.mu.Unlock()
|
||||
}
|
||||
|
||||
func nodeMemoryMB() string {
|
||||
|
||||
@@ -24,6 +24,25 @@ func fakeRuntime(t *testing.T, script string) {
|
||||
t.Setenv("CANVAS_PI_RUNTIME_DIR", dir)
|
||||
}
|
||||
|
||||
func TestSetProcessLimitDoesNotStopActiveWork(t *testing.T) {
|
||||
t.Cleanup(func() { SetProcessLimit(defaultAgentProcessLimit) })
|
||||
release, err := acquireAgentProcess(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
active, limit := SetProcessLimit(1)
|
||||
if active != 1 || limit != 1 {
|
||||
t.Fatalf("active=%d limit=%d, want active 1 and limit 1", active, limit)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Millisecond)
|
||||
defer cancel()
|
||||
if _, err := acquireAgentProcess(ctx); err == nil {
|
||||
t.Fatal("lowered limit still admitted another process")
|
||||
}
|
||||
release()
|
||||
SetProcessLimit(defaultAgentProcessLimit)
|
||||
}
|
||||
|
||||
func noopBridge() Bridge {
|
||||
handler := func(context.Context, map[string]json.RawMessage) (any, error) { return map[string]any{"ok": true}, nil }
|
||||
return Bridge{Model: handler, Tool: handler, Event: handler}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,18 @@ package app
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
agentruntime "infinite-canvas/backend/internal/agent/runtime"
|
||||
"infinite-canvas/backend/internal/agent/yingceagent"
|
||||
"infinite-canvas/backend/internal/buildinfo"
|
||||
"infinite-canvas/backend/internal/model"
|
||||
"infinite-canvas/backend/internal/platform"
|
||||
"infinite-canvas/backend/internal/repository"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
@@ -28,9 +32,23 @@ type AdminSystemPerformance struct {
|
||||
Disk SystemPerformanceDisk `json:"disk"`
|
||||
Database repository.DatabaseRuntimeStats `json:"database"`
|
||||
Redis SystemPerformanceRedis `json:"redis"`
|
||||
Agents []SystemPerformanceAgent `json:"agents"`
|
||||
Build buildinfo.Info `json:"build"`
|
||||
}
|
||||
|
||||
type SystemPerformanceAgent struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Mode string `json:"mode"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
Healthy bool `json:"healthy"`
|
||||
Active int `json:"active"`
|
||||
Limit int `json:"limit"`
|
||||
Queued int `json:"queued"`
|
||||
ConfiguredLimit int `json:"configuredLimit"`
|
||||
StatusMessage string `json:"statusMessage,omitempty"`
|
||||
}
|
||||
|
||||
type SystemPerformanceHost struct {
|
||||
Hostname string `json:"hostname"`
|
||||
OS string `json:"os"`
|
||||
@@ -149,17 +167,98 @@ func (s *Service) AdminSystemPerformance(ctx context.Context, actor *model.User)
|
||||
disk := collectSystemDisk(s.dataDir)
|
||||
databaseStats, databaseErr := s.repo.DatabaseRuntimeStats(ctx)
|
||||
redisStats := s.collectRedisPerformance(ctx)
|
||||
agents := s.collectAgentServices(ctx)
|
||||
status := "healthy"
|
||||
if databaseErr != nil || !databaseStats.Connected || !disk.Available || !disk.Writable || s.ValidateRuntime() != nil || (redisStats.Configured && !redisStats.Connected) {
|
||||
if databaseErr != nil || !databaseStats.Connected || !disk.Available || !disk.Writable || s.ValidateRuntime() != nil || (redisStats.Configured && !redisStats.Connected) || agentServicesDegraded(agents) {
|
||||
status = "degraded"
|
||||
}
|
||||
return &AdminSystemPerformance{
|
||||
CollectedAt: time.Now(), Status: status,
|
||||
Host: SystemPerformanceHost{Hostname: hostname, OS: runtime.GOOS, Arch: runtime.GOARCH, CPUCores: runtime.NumCPU(), GOMAXPROCS: runtime.GOMAXPROCS(0), ProcessID: os.Getpid(), UptimeSeconds: int64(time.Since(systemProcessStartedAt).Seconds()), Goroutines: runtime.NumGoroutine(), ActiveWorkerTasks: s.ActiveWorkerTasks(), LoadAverage: load, LoadAverageAvailable: loadAvailable},
|
||||
Memory: memory, Disk: disk, Database: databaseStats, Redis: redisStats, Build: buildinfo.Current(),
|
||||
Memory: memory, Disk: disk, Database: databaseStats, Redis: redisStats, Agents: agents, Build: buildinfo.Current(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type AgentSessionLimitRequest struct {
|
||||
MaxSessions int `json:"maxSessions"`
|
||||
}
|
||||
|
||||
func (s *Service) UpdateAgentSessionLimit(ctx context.Context, actor *model.User, maxSessions int) (*SystemPerformanceAgent, error) {
|
||||
if err := s.RequireAdmin(actor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if maxSessions < platform.MinRuntimeAgentSessions || maxSessions > platform.MaxRuntimeAgentSessions {
|
||||
return nil, BadAuthRequest(fmt.Sprintf("Agent 同时对话上限必须是 %d-%d 的整数", platform.MinRuntimeAgentSessions, platform.MaxRuntimeAgentSessions))
|
||||
}
|
||||
policy, err := s.RuntimePolicy()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
policy.Task.AgentMaxSessions = maxSessions
|
||||
if _, err := s.UpdateRuntimePolicySetting(actor, policy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.applyAgentSessionLimit(ctx, maxSessions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
agents := s.collectAgentServices(ctx)
|
||||
if len(agents) == 0 {
|
||||
return nil, fmt.Errorf("Agent 服务状态不可用")
|
||||
}
|
||||
return &agents[0], nil
|
||||
}
|
||||
|
||||
func (s *Service) applyAgentSessionLimit(ctx context.Context, maxSessions int) error {
|
||||
agentruntime.SetProcessLimit(maxSessions)
|
||||
endpoint := strings.TrimSpace(os.Getenv("YINGCE_AGENT_URL"))
|
||||
if endpoint == "" {
|
||||
return nil
|
||||
}
|
||||
return yingceagent.SetLimit(ctx, endpoint, os.Getenv("YINGCE_AGENT_TOKEN"), maxSessions)
|
||||
}
|
||||
|
||||
func (s *Service) collectAgentServices(ctx context.Context) []SystemPerformanceAgent {
|
||||
configured := platform.DefaultRuntimeAgentSessions
|
||||
if policy, err := s.RuntimePolicy(); err == nil && policy.Task.AgentMaxSessions > 0 {
|
||||
configured = policy.Task.AgentMaxSessions
|
||||
}
|
||||
endpoint := strings.TrimSpace(os.Getenv("YINGCE_AGENT_URL"))
|
||||
if endpoint == "" {
|
||||
active, limit := agentruntime.ProcessUsage()
|
||||
return []SystemPerformanceAgent{{
|
||||
ID: "embedded", Name: "画布 Agent", Mode: "embedded", Endpoint: "本进程",
|
||||
Healthy: true, Active: active, Limit: limit, ConfiguredLimit: configured,
|
||||
StatusMessage: "本地开发由后端进程直接启动,不单独占用容器。",
|
||||
}}
|
||||
}
|
||||
service := SystemPerformanceAgent{
|
||||
ID: "yingce-agent", Name: "画布 Agent", Mode: "remote", Endpoint: endpoint,
|
||||
ConfiguredLimit: configured,
|
||||
}
|
||||
status, err := yingceagent.Inspect(ctx, endpoint)
|
||||
if err != nil {
|
||||
service.StatusMessage = "独立 Agent 服务不可达。正在进行的对话会失败,不会自动改回本进程执行。"
|
||||
return []SystemPerformanceAgent{service}
|
||||
}
|
||||
service.Healthy = status.OK
|
||||
service.Active = status.Active
|
||||
service.Limit = status.Limit
|
||||
service.Queued = status.Queued
|
||||
if status.Limit != configured {
|
||||
service.StatusMessage = "运行中的上限与已保存配置不一致,重新保存一次即可应用。"
|
||||
}
|
||||
return []SystemPerformanceAgent{service}
|
||||
}
|
||||
|
||||
func agentServicesDegraded(agents []SystemPerformanceAgent) bool {
|
||||
for _, agent := range agents {
|
||||
if !agent.Healthy {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func collectSystemMemory() SystemPerformanceMemory {
|
||||
var stats runtime.MemStats
|
||||
runtime.ReadMemStats(&stats)
|
||||
|
||||
@@ -161,6 +161,7 @@ func TestRuntimePolicyBackfillsAgentStepLimitsForLegacyJSON(t *testing.T) {
|
||||
task := value["task"].(map[string]any)
|
||||
delete(task, "agentStepMaxOutputTokens")
|
||||
delete(task, "agentStepTimeoutSeconds")
|
||||
delete(task, "agentMaxSessions")
|
||||
trimmed, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -176,6 +177,9 @@ func TestRuntimePolicyBackfillsAgentStepLimitsForLegacyJSON(t *testing.T) {
|
||||
if effective.Task.AgentStepMaxOutputTokens != platform.DefaultRuntimeAgentStepOutputTokens {
|
||||
t.Fatalf("legacy json 未回填单步输出上限: %d", effective.Task.AgentStepMaxOutputTokens)
|
||||
}
|
||||
if effective.Task.AgentMaxSessions != platform.DefaultRuntimeAgentSessions {
|
||||
t.Fatalf("legacy json 未回填同时对话上限: %d", effective.Task.AgentMaxSessions)
|
||||
}
|
||||
if effective.Task.AgentStepTimeoutSeconds != platform.DefaultRuntimeAgentStepTimeout {
|
||||
t.Fatalf("legacy json 未回填单步超时: %d", effective.Task.AgentStepTimeoutSeconds)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -166,6 +167,7 @@ func (s *Service) StartWorker() {
|
||||
s.startResourceDeletionWorker(ctx)
|
||||
s.startSkillSyncWorker(ctx)
|
||||
s.startPaymentWorker(ctx)
|
||||
go s.syncAgentSessionLimit()
|
||||
s.runWorkerLoop(func(ctx context.Context) {
|
||||
ticker := time.NewTicker(3 * time.Second)
|
||||
defer ticker.Stop()
|
||||
@@ -180,6 +182,19 @@ func (s *Service) StartWorker() {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) syncAgentSessionLimit() {
|
||||
policy, err := s.RuntimePolicy()
|
||||
if err != nil {
|
||||
slog.Warn("agent session limit load failed", "error", err)
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
if err := s.applyAgentSessionLimit(ctx, policy.Task.AgentMaxSessions); err != nil {
|
||||
slog.Warn("agent session limit apply failed", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) BeginDrain() { s.backgroundWorkers().BeginDrain() }
|
||||
|
||||
func (s *Service) StopWorker(ctx context.Context) error { return s.backgroundWorkers().Stop(ctx) }
|
||||
|
||||
@@ -23,6 +23,26 @@ func RegisterAdminSystemPerformanceRoutes(r *gin.RouterGroup, svc *service.Servi
|
||||
ok(c, result)
|
||||
})
|
||||
|
||||
r.PUT("/admin/system-performance/agent-limit", func(c *gin.Context) {
|
||||
actor, err := currentUser(c, svc)
|
||||
if err != nil {
|
||||
failService(c, err)
|
||||
return
|
||||
}
|
||||
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 4<<10)
|
||||
var request service.AgentSessionLimitRequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
failService(c, service.BadAuthRequest("Agent 并发请求无效"))
|
||||
return
|
||||
}
|
||||
result, err := svc.UpdateAgentSessionLimit(c.Request.Context(), actor, request.MaxSessions)
|
||||
if err != nil {
|
||||
failService(c, err)
|
||||
return
|
||||
}
|
||||
ok(c, result)
|
||||
})
|
||||
|
||||
r.POST("/admin/system-performance/cache/clear", func(c *gin.Context) {
|
||||
actor, err := currentUser(c, svc)
|
||||
if err != nil {
|
||||
|
||||
@@ -14,6 +14,7 @@ func TestAdminSystemPerformanceRoutesAreRegistered(t *testing.T) {
|
||||
RegisterAdminSystemPerformanceRoutes(router.Group("/api"), &service.Service{})
|
||||
wanted := map[string]bool{
|
||||
"GET /api/admin/system-performance": false,
|
||||
"PUT /api/admin/system-performance/agent-limit": false,
|
||||
"POST /api/admin/system-performance/cache/clear": false,
|
||||
}
|
||||
for _, route := range router.Routes() {
|
||||
|
||||
@@ -36,6 +36,9 @@ const (
|
||||
MinRuntimeAgentStepTimeoutSeconds = 30
|
||||
MaxRuntimeAgentStepTimeoutSeconds = 3_600
|
||||
DefaultRuntimeAgentStepTimeout = 0
|
||||
MinRuntimeAgentSessions = 1
|
||||
MaxRuntimeAgentSessions = 64
|
||||
DefaultRuntimeAgentSessions = 30
|
||||
)
|
||||
|
||||
// agentStepMaxOutputTokensDefault 读取部署级覆盖并按同一套范围夹取;环境变量无法表达"不限制"(0),
|
||||
@@ -90,6 +93,8 @@ type RuntimeTaskPolicy struct {
|
||||
AgentStepMaxOutputTokens int `json:"agentStepMaxOutputTokens"`
|
||||
// AgentStepTimeoutSeconds 是画布 Agent 单步模型调用的秒级墙钟;0 表示沿用文本任务超时。
|
||||
AgentStepTimeoutSeconds int `json:"agentStepTimeoutSeconds"`
|
||||
// AgentMaxSessions 是同时进行中的画布 Agent 轮次上限。审批等待不占用名额。
|
||||
AgentMaxSessions int `json:"agentMaxSessions"`
|
||||
}
|
||||
|
||||
type RuntimeRequestPolicy struct {
|
||||
@@ -173,6 +178,7 @@ func DefaultRuntimePolicy() RuntimePolicySetting {
|
||||
DefaultTimeoutMinutes: 10,
|
||||
AgentStepMaxOutputTokens: agentStepMaxOutputTokensDefault(),
|
||||
AgentStepTimeoutSeconds: agentStepTimeoutSecondsDefault(),
|
||||
AgentMaxSessions: DefaultRuntimeAgentSessions,
|
||||
},
|
||||
Request: RuntimeRequestPolicy{
|
||||
TaskCreatePerMinute: 30,
|
||||
@@ -213,6 +219,7 @@ func selfUseRuntimePolicy() RuntimePolicySetting {
|
||||
AudioTimeoutMinutes: maxRuntimeTimeoutMinutes, VideoTimeoutMinutes: maxRuntimeTimeoutMinutes,
|
||||
StoryboardTimeoutMinutes: maxRuntimeTimeoutMinutes, DefaultTimeoutMinutes: maxRuntimeTimeoutMinutes,
|
||||
AgentStepMaxOutputTokens: MaxRuntimeAgentStepOutputTokens, AgentStepTimeoutSeconds: MaxRuntimeAgentStepTimeoutSeconds,
|
||||
AgentMaxSessions: MaxRuntimeAgentSessions,
|
||||
}
|
||||
value.Request = RuntimeRequestPolicy{
|
||||
TaskCreatePerMinute: maxRuntimeRate,
|
||||
@@ -437,6 +444,9 @@ func validateRuntimePolicy(value RuntimePolicySetting) error {
|
||||
if task.AgentStepTimeoutSeconds != 0 && (task.AgentStepTimeoutSeconds < MinRuntimeAgentStepTimeoutSeconds || task.AgentStepTimeoutSeconds > MaxRuntimeAgentStepTimeoutSeconds) {
|
||||
return kernel.BadAuthRequest(fmt.Sprintf("Agent 单步超时必须是 0 或 %d-%d 秒的整数 (0 表示沿用文本任务超时)", MinRuntimeAgentStepTimeoutSeconds, MaxRuntimeAgentStepTimeoutSeconds))
|
||||
}
|
||||
if task.AgentMaxSessions < MinRuntimeAgentSessions || task.AgentMaxSessions > MaxRuntimeAgentSessions {
|
||||
return kernel.BadAuthRequest(fmt.Sprintf("Agent 同时对话上限必须是 %d-%d 的整数", MinRuntimeAgentSessions, MaxRuntimeAgentSessions))
|
||||
}
|
||||
request := value.Request
|
||||
for label, item := range map[string]int{
|
||||
"任务创建频控": request.TaskCreatePerMinute,
|
||||
|
||||
@@ -27,6 +27,7 @@ type (
|
||||
AdminAuditPage = app.AdminAuditPage
|
||||
AdminCacheClearGroupResult = app.AdminCacheClearGroupResult
|
||||
AdminCacheClearRequest = app.AdminCacheClearRequest
|
||||
AgentSessionLimitRequest = app.AgentSessionLimitRequest
|
||||
AdminCacheClearResult = app.AdminCacheClearResult
|
||||
AdminChannelModelFetchResult = app.AdminChannelModelFetchResult
|
||||
AdminChannelModelImportRequest = app.AdminChannelModelImportRequest
|
||||
|
||||
Reference in New Issue
Block a user