mirror of
https://github.com/RuoJi6/ARTEX.git
synced 2026-10-27 17:03:49 +08:00
实现 #41:系统配置可设置 worker_pools,使本地模型与云端模型等各占用若干 Work Agent 会话并在同一任务内并行执行。空配置保持原 workers 遗留行为。
128 lines
3.6 KiB
Go
128 lines
3.6 KiB
Go
package server
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"strings"
|
||
)
|
||
|
||
// settingWorkerPools stores the multi-model Work Agent pool config as a JSON array.
|
||
// Empty / unset → legacy single-pool mode using settings.workers + unbound runtime.
|
||
const settingWorkerPools = "worker_pools"
|
||
|
||
// maxWorkerPoolTotal is the hard upper bound on sum(pool.workers), matching the
|
||
// per-task override ceiling used elsewhere so a misconfigured UI can't spawn dozens
|
||
// of LLM sessions per task.
|
||
const maxWorkerPoolTotal = 50
|
||
|
||
// WorkerPoolConfig is one LLM profile's share of concurrent Work Agents.
|
||
type WorkerPoolConfig struct {
|
||
ProfileID int64 `json:"profile_id"`
|
||
Workers int `json:"workers"`
|
||
}
|
||
|
||
// WorkerSlot is one Engine.Run worker loop: Name is the claim/session id (work#N);
|
||
// ProfileID 0 means unbound (legacy task-router resolution).
|
||
type WorkerSlot struct {
|
||
Name string
|
||
ProfileID int64
|
||
}
|
||
|
||
// validateWorkerPools checks a pools slice before persistence. Empty is allowed
|
||
// (clears multi-model mode). Non-empty requires every entry valid and total in 1..50.
|
||
func validateWorkerPools(pools []WorkerPoolConfig) error {
|
||
if len(pools) == 0 {
|
||
return nil
|
||
}
|
||
total := 0
|
||
for i, p := range pools {
|
||
if p.ProfileID <= 0 {
|
||
return fmt.Errorf("worker_pools[%d].profile_id 必须 >0", i)
|
||
}
|
||
if p.Workers <= 0 {
|
||
return fmt.Errorf("worker_pools[%d].workers 必须 >0", i)
|
||
}
|
||
total += p.Workers
|
||
}
|
||
if total > maxWorkerPoolTotal {
|
||
return fmt.Errorf("worker_pools 合计 Worker 数最多 %d(当前 %d)", maxWorkerPoolTotal, total)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// expandWorkerSlots turns pool configs into a spawn plan. Empty pools → legacy:
|
||
// N unbound slots named work#1..N where N = legacyWorkers (clamped to >=1).
|
||
func expandWorkerSlots(pools []WorkerPoolConfig, legacyWorkers int) []WorkerSlot {
|
||
if len(pools) == 0 {
|
||
n := legacyWorkers
|
||
if n <= 0 {
|
||
n = defaultWorkers
|
||
}
|
||
out := make([]WorkerSlot, n)
|
||
for i := 0; i < n; i++ {
|
||
out[i] = WorkerSlot{Name: fmt.Sprintf("work#%d", i+1), ProfileID: 0}
|
||
}
|
||
return out
|
||
}
|
||
var out []WorkerSlot
|
||
for _, p := range pools {
|
||
if p.ProfileID <= 0 || p.Workers <= 0 {
|
||
continue
|
||
}
|
||
for i := 0; i < p.Workers; i++ {
|
||
out = append(out, WorkerSlot{
|
||
Name: fmt.Sprintf("work#%d", len(out)+1),
|
||
ProfileID: p.ProfileID,
|
||
})
|
||
}
|
||
}
|
||
if len(out) == 0 {
|
||
return expandWorkerSlots(nil, legacyWorkers)
|
||
}
|
||
return out
|
||
}
|
||
|
||
// WorkerPools returns the configured multi-model pools (nil/empty = legacy mode).
|
||
// Invalid entries in the stored JSON are skipped.
|
||
func (m *Manager) WorkerPools() []WorkerPoolConfig {
|
||
if m == nil || m.pg == nil {
|
||
return nil
|
||
}
|
||
v, ok, err := m.pg.GetSetting(settingWorkerPools)
|
||
if err != nil || !ok || strings.TrimSpace(v) == "" {
|
||
return nil
|
||
}
|
||
var raw []WorkerPoolConfig
|
||
if json.Unmarshal([]byte(v), &raw) != nil {
|
||
return nil
|
||
}
|
||
out := make([]WorkerPoolConfig, 0, len(raw))
|
||
for _, p := range raw {
|
||
if p.ProfileID > 0 && p.Workers > 0 {
|
||
out = append(out, p)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// SetWorkerPools persists the multi-model pool config. An empty slice clears the
|
||
// setting (back to legacy single-pool Workers()). Applies to tasks started afterwards.
|
||
func (m *Manager) SetWorkerPools(pools []WorkerPoolConfig) error {
|
||
if err := validateWorkerPools(pools); err != nil {
|
||
return err
|
||
}
|
||
if len(pools) == 0 {
|
||
return m.pg.SetSetting(settingWorkerPools, "")
|
||
}
|
||
b, err := json.Marshal(pools)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return m.pg.SetSetting(settingWorkerPools, string(b))
|
||
}
|
||
|
||
// WorkerSlots returns the expanded spawn plan for Engine.Run (read per task start).
|
||
func (m *Manager) WorkerSlots() []WorkerSlot {
|
||
return expandWorkerSlots(m.WorkerPools(), m.Workers())
|
||
}
|