- 重复调用安全网(critical-tool-loop):指纹=name+canonical JSON(args);批内重复第二次跳过回喂
「duplicate call」;跨轮关键重复(同指纹重现)→首次 block+criticalSeen,再次连续重复→终止整轮
StopReasonToolLoopTerminated(替代裸 8 轮针对同调用的硬截止)。
- 并行/串行混合:invokeParallel 默认并行,信号量(ToolParallelism 默认4)限并发,结果按调用索引
写回按原序渲染;串行触发=Options.ToolExecution:sequential 或 SetToolExecutionMode(name,sequential)
(对齐 OpenClaw「一个 sequential 标记→整组串行」);goroutine 不泄漏、ctx 取消快速返回。
- 工具钩子:ToolLoopHooks(BatchAdmit/CallAdmit/AfterCall/AfterOutcome) 经 Options.Hooks 注入,
均 nil-tolerant;供后续敏感操作代审复用(design/11)。terminate 用 every 语义。
- status 分类:ToolError.Kind() 区分 argument-validation(JSON-Schema 失败)/execution;
失败消息渲染 {kind,message} 让模型区分重发参数 vs 执行失败。
- tool-call-repair:LenientJSONUnmarshal 修 fence 包裹/尾逗号畸形;agent.extractToolCalls 与
loop.decodeToolCalls 走它;不可修复退化为文本不崩整轮。意图调用未区分回喂提示(后置)。
- tool_execution_update:流式路径 emitToolExecution 发 StreamKindToolExecution(start/end/end:error)。
go build/vet/test 全绿(46包)。TestRun_MaxRounds 改每轮不同参(原同 echo 被安全网拦,预期新行为)。
-race 受宿主 TSan VMA 限制无法运行(与代码无关)。
69 lines
3.3 KiB
Go
69 lines
3.3 KiB
Go
package loop
|
|
|
|
import (
|
|
"log/slog"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/agent"
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/guardrail"
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/memory"
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/persist"
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/tool"
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/trace"
|
|
)
|
|
|
|
// Options configures a Loop. All fields are optional and nil is tolerated until
|
|
// the step that actually needs the field: the agent is required for Run (Run
|
|
// returns ErrNilAgent otherwise); a missing tool dispatcher causes a requested
|
|
// call to be fed back as an error; a missing retriever/persona skips memory
|
|
// injection; a missing extractor/store skips the memory hook; a missing
|
|
// checkpoint skips persistence; a missing tracer skips tracing.
|
|
type Options struct {
|
|
// Agent is the loop's execution unit: an instruction string plus a backend
|
|
// adapter, an optional tool dispatcher and optional guardrails.
|
|
Agent *agent.Agent
|
|
// Tools is the tool dispatcher used to execute model-requested calls. When
|
|
// nil the loop falls back to the agent's own dispatcher (if any).
|
|
Tools *tool.Dispatcher
|
|
// Memory is the long-term memory store used to persist extracted items.
|
|
Memory *memory.Store
|
|
// Retriever injects relevant memories into the first-turn context.
|
|
Retriever *memory.Retriever
|
|
// WorkingMemory is the per-session sliding window, provided for the
|
|
// orchestrator to inspect; the loop does not mutate it.
|
|
WorkingMemory *memory.WorkingMemory
|
|
// Extractor derives memory items to persist after every step.
|
|
Extractor memory.Extractor
|
|
// Checkpoint persists a durable snapshot of the loop state at every step.
|
|
Checkpoint *persist.Store
|
|
// Tracer emits a structured span for every step, LLM call and tool call.
|
|
Tracer *trace.Tracer
|
|
// Guards validates request/response payloads on the streaming path, where
|
|
// RunStep is bypassed. On the non-streaming path the agent runs its own
|
|
// guardrails inside RunStep.
|
|
Guards *guardrail.Pipeline
|
|
// PersonaID scopes memory retrieval and extraction. Blank skips both.
|
|
PersonaID string
|
|
// MaxRounds is the tool-loop upper bound. Non-positive selects
|
|
// DefaultMaxRounds (8).
|
|
MaxRounds int
|
|
// OnStream, when non-nil, switches the loop to the streaming path: the model
|
|
// adapter's ChatStream is driven directly and each structured stream event is
|
|
// forwarded to this callback (model.StreamEvent), so the frontend can render
|
|
// body text, tool calls and (eventually) reasoning segments independently.
|
|
OnStream func(model.StreamEvent)
|
|
// ToolExecution selects how a batch of tool calls is run: "parallel" (the
|
|
// default) or "sequential". Empty means parallel.
|
|
ToolExecution string
|
|
// ToolParallelism caps how many tool calls run concurrently when ToolExecution
|
|
// is "parallel". Non-positive selects DefaultToolParallelism (4).
|
|
ToolParallelism int
|
|
// Hooks installs optional per-batch/per-call tool-loop hooks. Every field is
|
|
// nil-tolerant; these are reserved for "敏感操作代审" (design/11) and the
|
|
// future asynchronous-tool path. A zero Hooks value is a no-op.
|
|
Hooks ToolLoopHooks
|
|
// Log is the base logger. Nil is replaced with a discard logger tagged with
|
|
// source "runtime/loop".
|
|
Log *slog.Logger
|
|
}
|