Merge branch 'feat/noa-experimental':noa 实验性上下文压缩
This commit is contained in:
commit
95bcd4ef9c
13 files changed
+181
-6
No files matched your search
@@ -33,6 +33,7 @@ type ChatAgent struct {
|
|||||||
webSearch WebSearchOpts
|
webSearch WebSearchOpts
|
||||||
guard *guard.Guard // optional; nil disables intercept hooks for chat
|
guard *guard.Guard // optional; nil disables intercept hooks for chat
|
||||||
nonStreamingFn func() bool // resolver: use non-streaming (Complete) path? (nil = streaming)
|
nonStreamingFn func() bool // resolver: use non-streaming (Complete) path? (nil = streaming)
|
||||||
|
noaEnabledFn func() bool // resolver: use experimental noa compaction? (nil = off)
|
||||||
maxTokensFn func() int // resolver: per-reply output cap (nil/0 = send no cap)
|
maxTokensFn func() int // resolver: per-reply output cap (nil/0 = send no cap)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +47,11 @@ func (c *ChatAgent) SetNonStreaming(fn func() bool) { c.nonStreamingFn = fn }
|
|||||||
|
|
||||||
func (c *ChatAgent) nonStreaming() bool { return c.nonStreamingFn != nil && c.nonStreamingFn() }
|
func (c *ChatAgent) nonStreaming() bool { return c.nonStreamingFn != nil && c.nonStreamingFn() }
|
||||||
|
|
||||||
|
// SetNoaEnabled wires a resolver deciding whether chat runs use the experimental
|
||||||
|
// noa context-compression mechanism. nil/unset = off (built-in compaction). Read
|
||||||
|
// per run so the settings toggle takes effect without rebuilding the agent.
|
||||||
|
func (c *ChatAgent) SetNoaEnabled(fn func() bool) { c.noaEnabledFn = fn }
|
||||||
|
|
||||||
// SetMaxTokens wires a resolver for the per-reply output cap. nil/unset or 0 =
|
// SetMaxTokens wires a resolver for the per-reply output cap. nil/unset or 0 =
|
||||||
// send no cap and let the endpoint decide. Read per run, like nonStreaming.
|
// send no cap and let the endpoint decide. Read per run, like nonStreaming.
|
||||||
func (c *ChatAgent) SetMaxTokens(fn func() int) { c.maxTokensFn = fn }
|
func (c *ChatAgent) SetMaxTokens(fn func() int) { c.maxTokensFn = fn }
|
||||||
@@ -161,6 +167,8 @@ func (c *ChatAgent) Chat(ctx context.Context, agentKey, sessionID, message strin
|
|||||||
opts.Transcript = c.tx
|
opts.Transcript = c.tx
|
||||||
opts.SessionID = sessionID
|
opts.SessionID = sessionID
|
||||||
}
|
}
|
||||||
|
// 实验功能:开启后由 noa 接管上下文压缩(归档集中在 <workDir>/noa/<SessionID> 下,持久)。
|
||||||
|
enableNoa(&opts, c.noaEnabledFn, c.workDir, "chat-"+sessionID, noaWarn("chat-"+sessionID))
|
||||||
ctx = attachSideCapture(ctx, &opts)
|
ctx = attachSideCapture(ctx, &opts)
|
||||||
s := agentcore.NewSession(opts)
|
s := agentcore.NewSession(opts)
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|||||||
@@ -31,9 +31,15 @@ type MainAgent struct {
|
|||||||
workDir string // shared work dir (surfaced in prompt as artifact-output target)
|
workDir string // shared work dir (surfaced in prompt as artifact-output target)
|
||||||
steerWork func(intentID int64, msg string) error // engine callback: steer a running work (nil = off)
|
steerWork func(intentID int64, msg string) error // engine callback: steer a running work (nil = off)
|
||||||
nonStreamingFn func() bool // resolver: use non-streaming (Complete) path? (nil = streaming)
|
nonStreamingFn func() bool // resolver: use non-streaming (Complete) path? (nil = streaming)
|
||||||
|
noaEnabledFn func() bool // resolver: use experimental noa compaction? (nil = off)
|
||||||
maxTokensFn func() int // resolver: per-reply output cap (nil/0 = send no cap)
|
maxTokensFn func() int // resolver: per-reply output cap (nil/0 = send no cap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetNoaEnabled wires a resolver deciding whether runs use the experimental noa
|
||||||
|
// context-compression mechanism. nil/unset = off (built-in compaction). Read per
|
||||||
|
// run so the settings toggle takes effect without rebuilding the agent.
|
||||||
|
func (m *MainAgent) SetNoaEnabled(fn func() bool) { m.noaEnabledFn = fn }
|
||||||
|
|
||||||
// SetNonStreaming wires a resolver deciding whether runs use the non-streaming
|
// SetNonStreaming wires a resolver deciding whether runs use the non-streaming
|
||||||
// model path (true = non-streaming). nil/unset = streaming (default).
|
// model path (true = non-streaming). nil/unset = streaming (default).
|
||||||
func (m *MainAgent) SetNonStreaming(fn func() bool) { m.nonStreamingFn = fn }
|
func (m *MainAgent) SetNonStreaming(fn func() bool) { m.nonStreamingFn = fn }
|
||||||
@@ -167,6 +173,13 @@ func (m *MainAgent) Chat(ctx context.Context, taskID int64, mainSeg int, as *db.
|
|||||||
opts.SessionID = fmt.Sprintf("exp%d-main-s%d", ts.ID(), mainSeg)
|
opts.SessionID = fmt.Sprintf("exp%d-main-s%d", ts.ID(), mainSeg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 实验功能:开启后由 noa 接管上下文压缩(归档集中在 <workDir>/noa/<SessionID> 下,持久)。
|
||||||
|
// session id 与 transcript 同规则(分段感知),使归档与恢复对齐。
|
||||||
|
noaSession := fmt.Sprintf("exp%d-main", ts.ID())
|
||||||
|
if mainSeg > 0 {
|
||||||
|
noaSession = fmt.Sprintf("exp%d-main-s%d", ts.ID(), mainSeg)
|
||||||
|
}
|
||||||
|
enableNoa(&opts, m.noaEnabledFn, m.workDir, noaSession, noaWarn(noaSession))
|
||||||
ctx = attachSideCapture(ctx, &opts)
|
ctx = attachSideCapture(ctx, &opts)
|
||||||
s := agentcore.NewSession(opts)
|
s := agentcore.NewSession(opts)
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/Autumn-27/norma/agentcore"
|
||||||
|
"github.com/Autumn-27/norma/noaadapter"
|
||||||
|
)
|
||||||
|
|
||||||
|
// noaWarn returns a diagnostics sink tagging non-fatal noa messages with the
|
||||||
|
// session, routed through the package logger (agents have no per-instance one).
|
||||||
|
func noaWarn(session string) func(string) {
|
||||||
|
return func(msg string) { log.Printf("[noa] %s: %s", session, msg) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// noa 是 norma v0.4.0 引入的「模型驱动上下文压缩」机制,作为平台实验功能由用户在
|
||||||
|
// 系统设置中开关。它与内置 compaction 互斥:noaadapter.Enable 是唯一入口,一次挂上
|
||||||
|
// 上下文接管器(Compactor)、Compress 工具与三段常驻提示词,不调用 Enable 即为关闭
|
||||||
|
// (内置 compaction 照常工作)。开关由每个 agent 注入的 noaEnabledFn 解析,每 run 读
|
||||||
|
// 一次,故切换只影响之后启动的 run,无需重建 agent。
|
||||||
|
|
||||||
|
// enableNoa 在解析器报告开启时把 noa 接入 opts。archiveRoot 是压缩原文的持久化基目录
|
||||||
|
// (取全局 workDir,各 agent 统一落在 <workDir>/noa 下,不随任务/意图目录分散),sessionID
|
||||||
|
// 命名其下的归档子目录(全局唯一,故同一基目录内不冲突)。
|
||||||
|
//
|
||||||
|
// noa 是实验功能:接入失败不得中断真实任务。发生错误时经 onWarn 上报并回退内置压缩。
|
||||||
|
// 启用成功时清掉 opts.Compaction,避免 agentcore 因「两个上下文管理器同时设置」告警。
|
||||||
|
func enableNoa(opts *agentcore.Options, enabled func() bool, archiveRoot, sessionID string, onWarn func(string)) {
|
||||||
|
if enabled == nil || !enabled() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if opts.OnWarn == nil {
|
||||||
|
opts.OnWarn = onWarn
|
||||||
|
}
|
||||||
|
if err := noaadapter.Enable(opts, noaadapter.Options{
|
||||||
|
ArchiveBaseDir: filepath.Join(archiveRoot, "noa"),
|
||||||
|
SessionID: sessionID,
|
||||||
|
OnWarn: onWarn,
|
||||||
|
}); err != nil {
|
||||||
|
if onWarn != nil {
|
||||||
|
onWarn("noa 压缩启用失败,回退内置压缩:" + err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Compactor 覆盖 Compaction,但两者并存时 agentcore 每次会告警;明确清掉。
|
||||||
|
opts.Compaction = nil
|
||||||
|
}
|
||||||
@@ -36,6 +36,7 @@ type Planner struct {
|
|||||||
workDir string // shared work dir (surfaced in prompt as artifact-output target)
|
workDir string // shared work dir (surfaced in prompt as artifact-output target)
|
||||||
injectConstraints func() bool // resolver: inject task operation constraints into system prompt? (nil = yes)
|
injectConstraints func() bool // resolver: inject task operation constraints into system prompt? (nil = yes)
|
||||||
nonStreamingFn func() bool // resolver: use non-streaming (Complete) path? (nil = streaming)
|
nonStreamingFn func() bool // resolver: use non-streaming (Complete) path? (nil = streaming)
|
||||||
|
noaEnabledFn func() bool // resolver: use experimental noa compaction? (nil = off)
|
||||||
maxTokensFn func() int // resolver: per-reply output cap (nil/0 = send no cap)
|
maxTokensFn func() int // resolver: per-reply output cap (nil/0 = send no cap)
|
||||||
compactor *Compactor // cold-node compaction (§7); nil = disabled
|
compactor *Compactor // cold-node compaction (§7); nil = disabled
|
||||||
|
|
||||||
@@ -64,6 +65,11 @@ func (p *Planner) SetNonStreaming(fn func() bool) { p.nonStreamingFn = fn }
|
|||||||
|
|
||||||
func (p *Planner) nonStreaming() bool { return p.nonStreamingFn != nil && p.nonStreamingFn() }
|
func (p *Planner) nonStreaming() bool { return p.nonStreamingFn != nil && p.nonStreamingFn() }
|
||||||
|
|
||||||
|
// SetNoaEnabled wires a resolver deciding whether runs use the experimental noa
|
||||||
|
// context-compression mechanism. nil/unset = off (built-in compaction). Read per
|
||||||
|
// run so the settings toggle takes effect without rebuilding the agent.
|
||||||
|
func (p *Planner) SetNoaEnabled(fn func() bool) { p.noaEnabledFn = fn }
|
||||||
|
|
||||||
// SetMaxTokens wires a resolver for the per-reply output cap. nil/unset or 0 =
|
// SetMaxTokens wires a resolver for the per-reply output cap. nil/unset or 0 =
|
||||||
// send no cap and let the endpoint decide. Read per run, like nonStreaming.
|
// send no cap and let the endpoint decide. Read per run, like nonStreaming.
|
||||||
func (p *Planner) SetMaxTokens(fn func() int) { p.maxTokensFn = fn }
|
func (p *Planner) SetMaxTokens(fn func() int) { p.maxTokensFn = fn }
|
||||||
@@ -430,6 +436,9 @@ func (p *Planner) Plan(ctx context.Context, taskID int64, as *db.AssetStore, ts
|
|||||||
opts.Transcript = p.tx
|
opts.Transcript = p.tx
|
||||||
opts.SessionID = fmt.Sprintf("exp%d-planner", ts.ID())
|
opts.SessionID = fmt.Sprintf("exp%d-planner", ts.ID())
|
||||||
}
|
}
|
||||||
|
// 实验功能:开启后由 noa 接管上下文压缩(归档集中在 <workDir>/noa/<SessionID> 下,持久)。
|
||||||
|
noaSession := fmt.Sprintf("exp%d-planner", ts.ID())
|
||||||
|
enableNoa(&opts, p.noaEnabledFn, p.workDir, noaSession, noaWarn(noaSession))
|
||||||
// 态势(刚完成的意图 + 完整图)现在拼进本轮 user 输入(见下方 input)。user 里还有
|
// 态势(刚完成的意图 + 完整图)现在拼进本轮 user 输入(见下方 input)。user 里还有
|
||||||
// 指令 + 跨唤醒待办(todo 是模型自己的规划便签,可再生,放 user 即可)。
|
// 指令 + 跨唤醒待办(todo 是模型自己的规划便签,可再生,放 user 即可)。
|
||||||
// 开场白按「本轮有无具体变动」分两种:有变动 → 指向下方【实际变动】块;无变动
|
// 开场白按「本轮有无具体变动」分两种:有变动 → 指向下方【实际变动】块;无变动
|
||||||
|
|||||||
@@ -78,6 +78,10 @@ type Worker struct {
|
|||||||
// path. Read per run so a profile/task toggle takes effect without rebuilding
|
// path. Read per run so a profile/task toggle takes effect without rebuilding
|
||||||
// the agent. nil = streaming (default).
|
// the agent. nil = streaming (default).
|
||||||
nonStreamingFn func() bool
|
nonStreamingFn func() bool
|
||||||
|
// noaEnabledFn resolves whether this run uses the experimental noa context-
|
||||||
|
// compression mechanism. Read per run, like nonStreaming. nil = off (built-in
|
||||||
|
// compaction).
|
||||||
|
noaEnabledFn func() bool
|
||||||
// maxTokensFn resolves the per-reply output cap in tokens, on the same
|
// maxTokensFn resolves the per-reply output cap in tokens, on the same
|
||||||
// per-run basis. nil or 0 = send no cap and let the endpoint decide.
|
// per-run basis. nil or 0 = send no cap and let the endpoint decide.
|
||||||
maxTokensFn func() int
|
maxTokensFn func() int
|
||||||
@@ -114,6 +118,11 @@ func (w *Worker) SetNonStreaming(fn func() bool) { w.nonStreamingFn = fn }
|
|||||||
|
|
||||||
func (w *Worker) nonStreaming() bool { return w.nonStreamingFn != nil && w.nonStreamingFn() }
|
func (w *Worker) nonStreaming() bool { return w.nonStreamingFn != nil && w.nonStreamingFn() }
|
||||||
|
|
||||||
|
// SetNoaEnabled wires a resolver deciding whether runs use the experimental noa
|
||||||
|
// context-compression mechanism. nil/unset = off (built-in compaction). Read per
|
||||||
|
// run so the settings toggle takes effect without rebuilding the agent.
|
||||||
|
func (w *Worker) SetNoaEnabled(fn func() bool) { w.noaEnabledFn = fn }
|
||||||
|
|
||||||
// SetMaxTokens wires a resolver for the per-reply output cap. nil/unset or 0 =
|
// SetMaxTokens wires a resolver for the per-reply output cap. nil/unset or 0 =
|
||||||
// send no cap and let the endpoint decide. Read per run, like nonStreaming.
|
// send no cap and let the endpoint decide. Read per run, like nonStreaming.
|
||||||
func (w *Worker) SetMaxTokens(fn func() int) { w.maxTokensFn = fn }
|
func (w *Worker) SetMaxTokens(fn func() int) { w.maxTokensFn = fn }
|
||||||
@@ -481,6 +490,9 @@ func (w *Worker) execute(ctx context.Context, name string, taskID int64, as *db.
|
|||||||
input = "开始执行 system 里领到的意图:只做它、只产生事实、assets、finding、做完即停。"
|
input = "开始执行 system 里领到的意图:只做它、只产生事实、assets、finding、做完即停。"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 实验功能:开启后由 noa 接管上下文压缩(归档集中在 <workDir>/noa/<SessionID> 下,持久)。
|
||||||
|
noaSession := WorkerSessionID(ts.ID(), intent.ID)
|
||||||
|
enableNoa(&opts, w.noaEnabledFn, w.workDir, noaSession, noaWarn(noaSession))
|
||||||
ctx = attachSideCapture(ctx, &opts)
|
ctx = attachSideCapture(ctx, &opts)
|
||||||
s := agentcore.NewSession(opts)
|
s := agentcore.NewSession(opts)
|
||||||
defer s.Close() // release the session's background-task manager (temp dir + processes)
|
defer s.Close() // release the session's background-task manager (temp dir + processes)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module github.com/Autumn-27/artex
|
|||||||
go 1.26.3
|
go 1.26.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Autumn-27/norma v0.3.8
|
github.com/Autumn-27/norma v0.4.0
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/jackc/pgx/v5 v5.10.0
|
github.com/jackc/pgx/v5 v5.10.0
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
github.com/Autumn-27/norma v0.3.7 h1:BdzVV3LUqN3zzaoL5cqY9aq0l/ucOYonSewQHhoL8Wo=
|
github.com/Autumn-27/norma v0.4.0 h1:kJkFu6z8kyLV8/eo0cuPIqCTCCVBDrSETe8JppeSf/g=
|
||||||
github.com/Autumn-27/norma v0.3.7/go.mod h1:BudWrf7iHl96FgHqFr/HwN3mKYmGjJDt+xL2ZHXfFZw=
|
github.com/Autumn-27/norma v0.4.0/go.mod h1:BudWrf7iHl96FgHqFr/HwN3mKYmGjJDt+xL2ZHXfFZw=
|
||||||
github.com/Autumn-27/norma v0.3.8 h1:MOz9bekMyQ29cgt728nZ4gq+HjpeDgQwCTd/7dtBvuc=
|
|
||||||
github.com/Autumn-27/norma v0.3.8/go.mod h1:BudWrf7iHl96FgHqFr/HwN3mKYmGjJDt+xL2ZHXfFZw=
|
|
||||||
github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
|
github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
|
||||||
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||||
|
|||||||
@@ -271,6 +271,10 @@ const (
|
|||||||
// 任务并发上限:开关 + 上限数。默认关闭;开启后默认上限 5(见 defaultConcurrencyLimit)。
|
// 任务并发上限:开关 + 上限数。默认关闭;开启后默认上限 5(见 defaultConcurrencyLimit)。
|
||||||
settingConcurrencyOn = "task_concurrency_enabled"
|
settingConcurrencyOn = "task_concurrency_enabled"
|
||||||
settingConcurrencyLimit = "task_concurrency_limit"
|
settingConcurrencyLimit = "task_concurrency_limit"
|
||||||
|
// 实验功能:noa 模型驱动上下文压缩(norma v0.4.0)。默认关闭——开启后平台接入的四类
|
||||||
|
// agent(planner/worker/主 agent/对话)由 noa 接管上下文压缩,取代内置 compaction。
|
||||||
|
// 每 run 读一次,切换只影响之后启动的 run。
|
||||||
|
settingNoaCompaction = "noa_compaction"
|
||||||
// defaultWebSearchBackend is used when web search is on but no backend was picked.
|
// defaultWebSearchBackend is used when web search is on but no backend was picked.
|
||||||
defaultWebSearchBackend = "ddgs"
|
defaultWebSearchBackend = "ddgs"
|
||||||
// deepSeekWebSearchBackend borrows the active LLM profile instead of its own
|
// deepSeekWebSearchBackend borrows the active LLM profile instead of its own
|
||||||
@@ -488,6 +492,19 @@ func (m *Manager) SetLLMRecordEnabled(on bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NoaCompactionEnabled reports whether the experimental noa context-compression
|
||||||
|
// mechanism is on (默认关;settings.noa_compaction). Read per agent run via the
|
||||||
|
// injected resolver, so a toggle takes effect on the next run without rebuild.
|
||||||
|
func (m *Manager) NoaCompactionEnabled() bool {
|
||||||
|
return m.pg.GetBool(settingNoaCompaction, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNoaCompaction persists the noa toggle. Effective on the next agent run —
|
||||||
|
// the resolver reads it per run, so no rebuild is needed.
|
||||||
|
func (m *Manager) SetNoaCompaction(on bool) error {
|
||||||
|
return m.pg.SetBool(settingNoaCompaction, on)
|
||||||
|
}
|
||||||
|
|
||||||
// LLMPoolEnabled reports whether LLM failover ("轮询") is on (默认关;
|
// LLMPoolEnabled reports whether LLM failover ("轮询") is on (默认关;
|
||||||
// settings.llm_pool_enabled). Read when the provider chain is built (applyLLM),
|
// settings.llm_pool_enabled). Read when the provider chain is built (applyLLM),
|
||||||
// so a change requires a rebuild — putSettings does that.
|
// so a change requires a rebuild — putSettings does that.
|
||||||
|
|||||||
@@ -428,6 +428,7 @@ func (s *Server) buildPlannerWorker(pinID *int64, gProv llm.Provider, gCfg agent
|
|||||||
wk.SetConstraintInject(s.constraintInjectWorker) // 操作约束注入 worker(可配置,默认开;每轮读)
|
wk.SetConstraintInject(s.constraintInjectWorker) // 操作约束注入 worker(可配置,默认开;每轮读)
|
||||||
wk.SetNonStreaming(nonStreamingResolver(wCfg)) // 该 profile 选非流式时走 Provider.Complete
|
wk.SetNonStreaming(nonStreamingResolver(wCfg)) // 该 profile 选非流式时走 Provider.Complete
|
||||||
wk.SetMaxTokens(maxTokensResolver(wCfg)) // 单次回复输出上限(0 = 不发)
|
wk.SetMaxTokens(maxTokensResolver(wCfg)) // 单次回复输出上限(0 = 不发)
|
||||||
|
wk.SetNoaEnabled(s.m.NoaCompactionEnabled) // 实验功能:noa 上下文压缩(每 run 读)
|
||||||
pProv, pCfg := s.providerForAgent("planner", pinID, gProv, gCfg)
|
pProv, pCfg := s.providerForAgent("planner", pinID, gProv, gCfg)
|
||||||
pl := agent.NewPlanner(pProv, pCfg.Model, s.m.dir, tx, pCfg.CompactionWindow(), s.agentMaxTurns("planner"))
|
pl := agent.NewPlanner(pProv, pCfg.Model, s.m.dir, tx, pCfg.CompactionWindow(), s.agentMaxTurns("planner"))
|
||||||
pl.SetFindingRecorder(s.evidenceStore())
|
pl.SetFindingRecorder(s.evidenceStore())
|
||||||
@@ -438,6 +439,7 @@ func (s *Server) buildPlannerWorker(pinID *int64, gProv llm.Provider, gCfg agent
|
|||||||
pl.SetConstraintInject(s.constraintInjectPlanner) // 操作约束注入 planner(可配置,默认开;每轮读)
|
pl.SetConstraintInject(s.constraintInjectPlanner) // 操作约束注入 planner(可配置,默认开;每轮读)
|
||||||
pl.SetNonStreaming(nonStreamingResolver(pCfg)) // 该 profile 选非流式时走 Provider.Complete
|
pl.SetNonStreaming(nonStreamingResolver(pCfg)) // 该 profile 选非流式时走 Provider.Complete
|
||||||
pl.SetMaxTokens(maxTokensResolver(pCfg)) // 单次回复输出上限(0 = 不发)
|
pl.SetMaxTokens(maxTokensResolver(pCfg)) // 单次回复输出上限(0 = 不发)
|
||||||
|
pl.SetNoaEnabled(s.m.NoaCompactionEnabled) // 实验功能:noa 上下文压缩(每 run 读)
|
||||||
// cold-digest §7: background cold-node compaction, on the planner's provider/
|
// cold-digest §7: background cold-node compaction, on the planner's provider/
|
||||||
// model (§4 uses the running agent's model). Uses Complete (non-streaming) for
|
// model (§4 uses the running agent's model). Uses Complete (non-streaming) for
|
||||||
// the one-shot body summarization.
|
// the one-shot body summarization.
|
||||||
@@ -509,6 +511,7 @@ func (s *Server) applyLLM(cfg agent.Config) error {
|
|||||||
s.mainAgent.SetSteerWork(s.engine.SteerWork) // steer_work:人对运行中 work 实时纠偏
|
s.mainAgent.SetSteerWork(s.engine.SteerWork) // steer_work:人对运行中 work 实时纠偏
|
||||||
s.mainAgent.SetNonStreaming(nonStreamingResolver(mCfg))
|
s.mainAgent.SetNonStreaming(nonStreamingResolver(mCfg))
|
||||||
s.mainAgent.SetMaxTokens(maxTokensResolver(mCfg))
|
s.mainAgent.SetMaxTokens(maxTokensResolver(mCfg))
|
||||||
|
s.mainAgent.SetNoaEnabled(s.m.NoaCompactionEnabled) // 实验功能:noa 上下文压缩(每 run 读)
|
||||||
// chat agent serves MANY custom agents by key → it holds the GLOBAL opts
|
// chat agent serves MANY custom agents by key → it holds the GLOBAL opts
|
||||||
// (backend/key) and gates Enabled per-conversation-agent at Chat time. 对话始终用激活配置。
|
// (backend/key) and gates Enabled per-conversation-agent at Chat time. 对话始终用激活配置。
|
||||||
s.chatAgent = agent.NewChatAgent(prov, cfg.Model, s.m.dir, tx, win) // chat page runner
|
s.chatAgent = agent.NewChatAgent(prov, cfg.Model, s.m.dir, tx, win) // chat page runner
|
||||||
@@ -517,6 +520,7 @@ func (s *Server) applyLLM(cfg agent.Config) error {
|
|||||||
s.chatAgent.SetGuard(s.chatGuard())
|
s.chatAgent.SetGuard(s.chatGuard())
|
||||||
s.chatAgent.SetNonStreaming(nonStreamingResolver(cfg))
|
s.chatAgent.SetNonStreaming(nonStreamingResolver(cfg))
|
||||||
s.chatAgent.SetMaxTokens(maxTokensResolver(cfg))
|
s.chatAgent.SetMaxTokens(maxTokensResolver(cfg))
|
||||||
|
s.chatAgent.SetNoaEnabled(s.m.NoaCompactionEnabled) // 实验功能:noa 上下文压缩(每 run 读)
|
||||||
s.llmProv = prov
|
s.llmProv = prov
|
||||||
s.llmCfg = cfg
|
s.llmCfg = cfg
|
||||||
s.llmOn = true
|
s.llmOn = true
|
||||||
@@ -700,6 +704,7 @@ func (s *Server) chatAgentForProfile(id int64) *agent.ChatAgent {
|
|||||||
ca.SetGuard(s.chatGuard())
|
ca.SetGuard(s.chatGuard())
|
||||||
ca.SetNonStreaming(nonStreamingResolver(cfg))
|
ca.SetNonStreaming(nonStreamingResolver(cfg))
|
||||||
ca.SetMaxTokens(maxTokensResolver(cfg))
|
ca.SetMaxTokens(maxTokensResolver(cfg))
|
||||||
|
ca.SetNoaEnabled(s.m.NoaCompactionEnabled) // 实验功能:noa 上下文压缩(每 run 读)
|
||||||
s.profMu.Lock()
|
s.profMu.Lock()
|
||||||
if ex := s.profChatAgents[id]; ex != nil { // lost the race → keep the winner
|
if ex := s.profChatAgents[id]; ex != nil { // lost the race → keep the winner
|
||||||
ca = ex
|
ca = ex
|
||||||
@@ -3212,6 +3217,9 @@ func (s *Server) settingsPayload() map[string]any {
|
|||||||
// 操作约束注入范围(默认都开):把本任务的 allow/deny 约束拼进对应 agent 的系统提示。
|
// 操作约束注入范围(默认都开):把本任务的 allow/deny 约束拼进对应 agent 的系统提示。
|
||||||
"constraints_inject_planner": s.constraintInjectPlanner(),
|
"constraints_inject_planner": s.constraintInjectPlanner(),
|
||||||
"constraints_inject_worker": s.constraintInjectWorker(),
|
"constraints_inject_worker": s.constraintInjectWorker(),
|
||||||
|
// 实验功能:noa 模型驱动上下文压缩(默认关)。开启后平台接入的四类 agent 由 noa
|
||||||
|
// 接管上下文压缩,取代内置 compaction;每 run 读一次,对之后启动的 run 生效。
|
||||||
|
"noa_compaction": s.m.NoaCompactionEnabled(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3257,6 +3265,8 @@ func (s *Server) putSettings(w http.ResponseWriter, r *http.Request) {
|
|||||||
// 操作约束注入范围开关(默认都开);即时生效(planner/worker 每轮读),无需重建 agent。
|
// 操作约束注入范围开关(默认都开);即时生效(planner/worker 每轮读),无需重建 agent。
|
||||||
ConstraintsInjectPlanner *bool `json:"constraints_inject_planner"`
|
ConstraintsInjectPlanner *bool `json:"constraints_inject_planner"`
|
||||||
ConstraintsInjectWorker *bool `json:"constraints_inject_worker"`
|
ConstraintsInjectWorker *bool `json:"constraints_inject_worker"`
|
||||||
|
// 实验功能:noa 上下文压缩开关(默认关);每 run 读,对之后启动的 run 生效,无需重建 agent。
|
||||||
|
NoaCompaction *bool `json:"noa_compaction"`
|
||||||
}
|
}
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
writeErr(w, 400, err.Error())
|
writeErr(w, 400, err.Error())
|
||||||
@@ -3274,6 +3284,13 @@ func (s *Server) putSettings(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if req.NoaCompaction != nil {
|
||||||
|
// 每 run 读的解析器,切换即时对之后启动的 run 生效,无需 applyLLM 重建。
|
||||||
|
if err := s.m.SetNoaCompaction(*req.NoaCompaction); err != nil {
|
||||||
|
writeErr(w, 500, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
if req.Workers != nil {
|
if req.Workers != nil {
|
||||||
if err := s.m.SetWorkers(*req.Workers); err != nil {
|
if err := s.m.SetWorkers(*req.Workers); err != nil {
|
||||||
writeErr(w, 400, err.Error())
|
writeErr(w, 400, err.Error())
|
||||||
|
|||||||
@@ -566,6 +566,7 @@ func (s *Server) agentsForTask(t *Task) *taskAgentBundle {
|
|||||||
wk.SetCompactionWindowResolver(workerRuntime.CompactionWindow)
|
wk.SetCompactionWindowResolver(workerRuntime.CompactionWindow)
|
||||||
wk.SetNonStreaming(workerRuntime.nonStreaming) // 按任务当前激活 profile 的流式开关(每轮读)
|
wk.SetNonStreaming(workerRuntime.nonStreaming) // 按任务当前激活 profile 的流式开关(每轮读)
|
||||||
wk.SetMaxTokens(workerRuntime.maxTokens) // 同上,输出上限也跟随当前激活 profile
|
wk.SetMaxTokens(workerRuntime.maxTokens) // 同上,输出上限也跟随当前激活 profile
|
||||||
|
wk.SetNoaEnabled(s.m.NoaCompactionEnabled) // 实验功能:noa 上下文压缩(平台级开关,每 run 读)
|
||||||
wk.SetRunTimeout(time.Duration(s.agentRunSeconds("worker")) * time.Second)
|
wk.SetRunTimeout(time.Duration(s.agentRunSeconds("worker")) * time.Second)
|
||||||
wk.SetProxy(s.m.ProxyAddr(), s.m.ProxyCACert())
|
wk.SetProxy(s.m.ProxyAddr(), s.m.ProxyCACert())
|
||||||
wk.SetWebSearch(s.webSearchFor("worker"))
|
wk.SetWebSearch(s.webSearchFor("worker"))
|
||||||
@@ -575,6 +576,7 @@ func (s *Server) agentsForTask(t *Task) *taskAgentBundle {
|
|||||||
pl.SetCompactionWindowResolver(plannerRuntime.CompactionWindow)
|
pl.SetCompactionWindowResolver(plannerRuntime.CompactionWindow)
|
||||||
pl.SetNonStreaming(plannerRuntime.nonStreaming)
|
pl.SetNonStreaming(plannerRuntime.nonStreaming)
|
||||||
pl.SetMaxTokens(plannerRuntime.maxTokens)
|
pl.SetMaxTokens(plannerRuntime.maxTokens)
|
||||||
|
pl.SetNoaEnabled(s.m.NoaCompactionEnabled) // 实验功能:noa 上下文压缩(平台级开关,每 run 读)
|
||||||
pl.SetKillWork(s.engine.KillWork)
|
pl.SetKillWork(s.engine.KillWork)
|
||||||
pl.SetSteerWork(s.engine.SteerWork)
|
pl.SetSteerWork(s.engine.SteerWork)
|
||||||
pl.SetProxy(s.m.ProxyAddr(), s.m.ProxyCACert())
|
pl.SetProxy(s.m.ProxyAddr(), s.m.ProxyCACert())
|
||||||
@@ -585,6 +587,7 @@ func (s *Server) agentsForTask(t *Task) *taskAgentBundle {
|
|||||||
main.SetCompactionWindowResolver(mainRuntime.CompactionWindow)
|
main.SetCompactionWindowResolver(mainRuntime.CompactionWindow)
|
||||||
main.SetNonStreaming(mainRuntime.nonStreaming)
|
main.SetNonStreaming(mainRuntime.nonStreaming)
|
||||||
main.SetMaxTokens(mainRuntime.maxTokens)
|
main.SetMaxTokens(mainRuntime.maxTokens)
|
||||||
|
main.SetNoaEnabled(s.m.NoaCompactionEnabled) // 实验功能:noa 上下文压缩(平台级开关,每 run 读)
|
||||||
main.SetProxy(s.m.ProxyAddr(), s.m.ProxyCACert())
|
main.SetProxy(s.m.ProxyAddr(), s.m.ProxyCACert())
|
||||||
main.SetWebSearch(s.webSearchFor("mainagent"))
|
main.SetWebSearch(s.webSearchFor("mainagent"))
|
||||||
main.SetSteerWork(s.engine.SteerWork) // steer_work:人对运行中 work 实时纠偏
|
main.SetSteerWork(s.engine.SteerWork) // steer_work:人对运行中 work 实时纠偏
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import { CpuIcon, KeyboardIcon, RadioTowerIcon, SearchIcon, ShieldAlertIcon } from "lucide-react";
|
import { CpuIcon, FlaskConicalIcon, KeyboardIcon, RadioTowerIcon, SearchIcon, ShieldAlertIcon } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -41,6 +41,8 @@ export default function SystemSettingsPage() {
|
|||||||
// 操作约束注入范围(默认都开)。
|
// 操作约束注入范围(默认都开)。
|
||||||
const [injectPlanner, setInjectPlanner] = React.useState(true);
|
const [injectPlanner, setInjectPlanner] = React.useState(true);
|
||||||
const [injectWorker, setInjectWorker] = React.useState(true);
|
const [injectWorker, setInjectWorker] = React.useState(true);
|
||||||
|
// 实验功能:noa 上下文压缩(默认关)。
|
||||||
|
const [noaCompaction, setNoaCompaction] = React.useState(false);
|
||||||
// 纯前端偏好:不走 /api/settings,直接读写 localStorage。
|
// 纯前端偏好:不走 /api/settings,直接读写 localStorage。
|
||||||
const sendMode = useChatSendMode();
|
const sendMode = useChatSendMode();
|
||||||
|
|
||||||
@@ -57,6 +59,7 @@ export default function SystemSettingsPage() {
|
|||||||
setWorkers(String(s.workers ?? 3));
|
setWorkers(String(s.workers ?? 3));
|
||||||
setInjectPlanner(s.constraints_inject_planner !== false);
|
setInjectPlanner(s.constraints_inject_planner !== false);
|
||||||
setInjectWorker(s.constraints_inject_worker !== false);
|
setInjectWorker(s.constraints_inject_worker !== false);
|
||||||
|
setNoaCompaction(!!s.noa_compaction);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const saveWorkers = () => {
|
const saveWorkers = () => {
|
||||||
@@ -146,6 +149,20 @@ export default function SystemSettingsPage() {
|
|||||||
.catch(() => setInjectWorker(!v)); // revert on failure
|
.catch(() => setInjectWorker(!v)); // revert on failure
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleNoaCompaction = (v: boolean) => {
|
||||||
|
setNoaCompaction(v); // optimistic
|
||||||
|
api
|
||||||
|
.setSettings({ noa_compaction: v })
|
||||||
|
.then((s) => {
|
||||||
|
apply(s);
|
||||||
|
toast.success(v ? "已开启 noa 上下文压缩(对之后启动的运行生效)" : "已关闭 noa 上下文压缩(恢复内置压缩)");
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
setNoaCompaction(!v); // revert on failure
|
||||||
|
toast.error(`保存失败:${(e as Error).message}`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Persist a web-search patch (enable and/or backend). Optimistic with refetch.
|
// Persist a web-search patch (enable and/or backend). Optimistic with refetch.
|
||||||
const saveWebSearch = (patch: Partial<Settings>) => {
|
const saveWebSearch = (patch: Partial<Settings>) => {
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
@@ -387,6 +404,34 @@ export default function SystemSettingsPage() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<Card className="mb-4 break-inside-avoid md:mb-6">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2 text-base">
|
||||||
|
<FlaskConicalIcon className="size-4" />
|
||||||
|
实验功能
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
尚在验证中的机制,默认关闭。可能改变 Agent 行为或影响稳定性,请在了解影响后启用。
|
||||||
|
<br />
|
||||||
|
<b>noa 上下文压缩</b>:由模型主动压缩长对话历史(norma v0.4.0)。开启后平台接入的四类 Agent(
|
||||||
|
<b>规划者 / 执行者 / 主 Agent / 对话</b>)改用 noa 接管上下文,取代内置压缩,
|
||||||
|
压缩原文会归档到任务工作目录下便于回溯。切换即时生效(对之后启动的运行生效),无需重建 Agent;
|
||||||
|
关闭后立即恢复内置压缩。
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex items-center justify-between gap-4">
|
||||||
|
<Label htmlFor="noa-compaction" className="text-sm font-normal text-muted-foreground">
|
||||||
|
noa 上下文压缩{noaCompaction ? " · 已开启" : " · 已关闭"}
|
||||||
|
</Label>
|
||||||
|
<Switch
|
||||||
|
id="noa-compaction"
|
||||||
|
checked={noaCompaction}
|
||||||
|
disabled={!loaded}
|
||||||
|
onCheckedChange={toggleNoaCompaction}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Card className="mb-4 break-inside-avoid md:mb-6">
|
<Card className="mb-4 break-inside-avoid md:mb-6">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2 text-base">
|
<CardTitle className="flex items-center gap-2 text-base">
|
||||||
|
|||||||
@@ -3326,6 +3326,7 @@ export const settings: Settings = {
|
|||||||
workers: 3,
|
workers: 3,
|
||||||
llm_pool_enabled: true,
|
llm_pool_enabled: true,
|
||||||
llm_pool_bind_fallback: false,
|
llm_pool_bind_fallback: false,
|
||||||
|
noa_compaction: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── LLM 轮询(故障转移)──────────────────────────────────────────────────────
|
// ── LLM 轮询(故障转移)──────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -913,6 +913,10 @@ export interface Settings {
|
|||||||
// 操作约束注入范围(默认都开):把任务的 allow/deny 约束拼进对应 agent 的系统提示。
|
// 操作约束注入范围(默认都开):把任务的 allow/deny 约束拼进对应 agent 的系统提示。
|
||||||
constraints_inject_planner?: boolean;
|
constraints_inject_planner?: boolean;
|
||||||
constraints_inject_worker?: boolean;
|
constraints_inject_worker?: boolean;
|
||||||
|
// 实验功能:noa 模型驱动上下文压缩(默认关)。开启后平台接入的四类 agent(planner/
|
||||||
|
// worker/主 agent/对话)由 noa 接管上下文压缩,取代内置 compaction;每 run 读一次,对
|
||||||
|
// 之后启动的 run 生效。
|
||||||
|
noa_compaction?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- LLM config ----
|
// ---- LLM config ----
|
||||||
|
|||||||
Reference in new issue
Block a user