mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-08-15 15:40:38 +02:00
163 lines
5.0 KiB
Go
163 lines
5.0 KiB
Go
package multiagent
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/cloudwego/eino/adk"
|
|
"github.com/cloudwego/eino/schema"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type einoTurnLoopRuntimeControl interface {
|
|
Run(context.Context)
|
|
PushInterruptContinue(string) bool
|
|
StopImmediate(string)
|
|
StopWhenIdle()
|
|
Wait() *adk.TurnLoopExitState[EinoTurnLoopItem, *schema.Message]
|
|
}
|
|
|
|
type einoTurnLoopRuntimeFactory func(EinoTurnLoopRuntimeConfig) einoTurnLoopRuntimeControl
|
|
|
|
type einoTurnLoopIteratorStarterConfig struct {
|
|
Context context.Context
|
|
Agent adk.Agent
|
|
ConversationID string
|
|
OrchMode string
|
|
Progress func(eventType, message string, data interface{})
|
|
Logger *zap.Logger
|
|
Store adk.CheckPointStore
|
|
CheckPointID string
|
|
InterruptTimeout time.Duration
|
|
NativeCancelCause *atomic.Value
|
|
UnregisterAgentCancel *func()
|
|
UnregisterTurnLoopInterrupt *func()
|
|
RuntimeCancelRegistrar AgentRuntimeCancelRegistrar
|
|
TurnLoopInterruptRegistrar AgentTurnLoopInterruptRegistrar
|
|
RuntimeFactory einoTurnLoopRuntimeFactory
|
|
}
|
|
|
|
type einoTurnLoopIteratorStarter struct {
|
|
cfg einoTurnLoopIteratorStarterConfig
|
|
}
|
|
|
|
func newEinoTurnLoopIteratorStarter(cfg einoTurnLoopIteratorStarterConfig) *einoTurnLoopIteratorStarter {
|
|
if cfg.RuntimeFactory == nil {
|
|
cfg.RuntimeFactory = func(runtimeCfg EinoTurnLoopRuntimeConfig) einoTurnLoopRuntimeControl {
|
|
return NewEinoTurnLoopRuntime(runtimeCfg)
|
|
}
|
|
}
|
|
return &einoTurnLoopIteratorStarter{cfg: cfg}
|
|
}
|
|
|
|
func (s *einoTurnLoopIteratorStarter) Start(runMsgs []adk.Message) *adk.AsyncIterator[*adk.AgentEvent] {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
callAndClearUnregister(s.cfg.UnregisterTurnLoopInterrupt)
|
|
callAndClearUnregister(s.cfg.UnregisterAgentCancel)
|
|
|
|
iter, gen := adk.NewAsyncIteratorPair[*adk.AgentEvent]()
|
|
eventsBridge := newEinoTurnLoopEventBridge(s.cfg.ConversationID, s.cfg.OrchMode, s.cfg.Progress, gen)
|
|
runtime := s.cfg.RuntimeFactory(EinoTurnLoopRuntimeConfig{
|
|
Agent: s.cfg.Agent,
|
|
InitialMessages: runMsgs,
|
|
Store: s.cfg.Store,
|
|
CheckpointID: s.turnLoopCheckpointID(),
|
|
EnableStreaming: true,
|
|
InterruptTimeout: s.cfg.InterruptTimeout,
|
|
OnAgentEvents: eventsBridge.OnAgentEvents,
|
|
})
|
|
s.bindTurnLoopInterrupt(runtime)
|
|
s.bindRuntimeCancel(runtime)
|
|
runtime.Run(s.cfg.Context)
|
|
runtime.StopWhenIdle()
|
|
go func() {
|
|
defer gen.Close()
|
|
state := runtime.Wait()
|
|
if state == nil || state.ExitReason == nil || eventsBridge.ForwardedError() {
|
|
return
|
|
}
|
|
gen.Send(&adk.AgentEvent{Err: state.ExitReason})
|
|
}()
|
|
return iter
|
|
}
|
|
|
|
func (s *einoTurnLoopIteratorStarter) turnLoopCheckpointID() string {
|
|
if s == nil || s.cfg.CheckPointID == "" {
|
|
return ""
|
|
}
|
|
return buildEinoTurnLoopCheckpointID(s.cfg.OrchMode)
|
|
}
|
|
|
|
func (s *einoTurnLoopIteratorStarter) bindTurnLoopInterrupt(runtime einoTurnLoopRuntimeControl) {
|
|
if s == nil || runtime == nil || s.cfg.TurnLoopInterruptRegistrar == nil || s.cfg.UnregisterTurnLoopInterrupt == nil {
|
|
return
|
|
}
|
|
*s.cfg.UnregisterTurnLoopInterrupt = s.cfg.TurnLoopInterruptRegistrar(func(note string) bool {
|
|
ok := runtime.PushInterruptContinue(note)
|
|
if ok {
|
|
s.emitInterruptContinueProgress(note)
|
|
}
|
|
return ok
|
|
})
|
|
}
|
|
|
|
func (s *einoTurnLoopIteratorStarter) bindRuntimeCancel(runtime einoTurnLoopRuntimeControl) {
|
|
if s == nil || runtime == nil || s.cfg.RuntimeCancelRegistrar == nil || s.cfg.UnregisterAgentCancel == nil {
|
|
return
|
|
}
|
|
*s.cfg.UnregisterAgentCancel = s.cfg.RuntimeCancelRegistrar(func(cause error) bool {
|
|
s.storeNativeCancelCause(cause)
|
|
if errors.Is(cause, ErrInterruptContinue) {
|
|
return runtime.PushInterruptContinue("")
|
|
}
|
|
runtime.StopImmediate("task_cancelled")
|
|
if s.cfg.Logger != nil {
|
|
s.cfg.Logger.Info("eino turn loop stop requested",
|
|
zap.String("conversation_id", s.cfg.ConversationID),
|
|
zap.String("orchestration", s.cfg.OrchMode),
|
|
zap.Error(cause))
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
|
|
func (s *einoTurnLoopIteratorStarter) storeNativeCancelCause(cause error) {
|
|
if s == nil || s.cfg.NativeCancelCause == nil || cause == nil {
|
|
return
|
|
}
|
|
s.cfg.NativeCancelCause.Store(cause)
|
|
}
|
|
|
|
func (s *einoTurnLoopIteratorStarter) emitInterruptContinueProgress(note string) {
|
|
if s == nil || s.cfg.Progress == nil {
|
|
return
|
|
}
|
|
trimmed := strings.TrimSpace(note)
|
|
s.cfg.Progress("user_interrupt_continue", einoTurnLoopInterruptTimelineSummary(note), map[string]interface{}{
|
|
"conversationId": s.cfg.ConversationID,
|
|
"rawReason": trimmed,
|
|
"emptyReason": trimmed == "",
|
|
"kind": "turn_loop_preempt",
|
|
"source": "eino",
|
|
"orchestration": s.cfg.OrchMode,
|
|
})
|
|
s.cfg.Progress("progress", "已将用户补充推入 Eino TurnLoop,正在等待安全点切换…", map[string]interface{}{
|
|
"conversationId": s.cfg.ConversationID,
|
|
"source": "eino",
|
|
"orchestration": s.cfg.OrchMode,
|
|
})
|
|
}
|
|
|
|
func callAndClearUnregister(target *func()) {
|
|
if target == nil || *target == nil {
|
|
return
|
|
}
|
|
(*target)()
|
|
*target = nil
|
|
}
|