mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-08-15 23:50:32 +02:00
179 lines
5.7 KiB
Go
179 lines
5.7 KiB
Go
package multiagent
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/cloudwego/eino/adk"
|
|
"github.com/cloudwego/eino/schema"
|
|
)
|
|
|
|
const (
|
|
einoTurnLoopInterruptPreemptTimeout = 3 * time.Second
|
|
einoTurnLoopIdleStop = 250 * time.Millisecond
|
|
)
|
|
|
|
// EinoTurnLoopItem is the conversation-level input unit consumed by an Eino
|
|
// TurnLoop. The item is gob-friendly so it can be checkpointed by TurnLoop when
|
|
// a CheckPointStore is configured.
|
|
type EinoTurnLoopItem struct {
|
|
Messages []*schema.Message
|
|
Kind string
|
|
Note string
|
|
}
|
|
|
|
// EinoTurnLoopRuntime wraps Eino's native TurnLoop with the semantics this
|
|
// project needs: persistent per-conversation runtime, user-supplement preempt,
|
|
// and graceful idle shutdown.
|
|
type EinoTurnLoopRuntime struct {
|
|
loop *adk.TurnLoop[EinoTurnLoopItem, *schema.Message]
|
|
interruptTimeout time.Duration
|
|
}
|
|
|
|
type EinoTurnLoopRuntimeConfig struct {
|
|
Agent adk.Agent
|
|
InitialMessages []*schema.Message
|
|
Store adk.CheckPointStore
|
|
CheckpointID string
|
|
EnableStreaming bool
|
|
PrepareAgent func(context.Context, *adk.TurnLoop[EinoTurnLoopItem, *schema.Message], []EinoTurnLoopItem) (adk.Agent, error)
|
|
OnAgentEvents func(context.Context, *adk.TurnContext[EinoTurnLoopItem, *schema.Message], *adk.AsyncIterator[*adk.AgentEvent]) error
|
|
InterruptTimeout time.Duration
|
|
}
|
|
|
|
func NewEinoTurnLoopRuntime(cfg EinoTurnLoopRuntimeConfig) *EinoTurnLoopRuntime {
|
|
timeout := cfg.InterruptTimeout
|
|
if timeout <= 0 {
|
|
timeout = einoTurnLoopInterruptPreemptTimeout
|
|
}
|
|
enableStreaming := cfg.EnableStreaming
|
|
prepareAgent := cfg.PrepareAgent
|
|
if prepareAgent == nil {
|
|
prepareAgent = func(context.Context, *adk.TurnLoop[EinoTurnLoopItem, *schema.Message], []EinoTurnLoopItem) (adk.Agent, error) {
|
|
return cfg.Agent, nil
|
|
}
|
|
}
|
|
loop := adk.NewTurnLoop[EinoTurnLoopItem, *schema.Message](adk.TurnLoopConfig[EinoTurnLoopItem, *schema.Message]{
|
|
Store: cfg.Store,
|
|
CheckpointID: cfg.CheckpointID,
|
|
GenInput: func(ctx context.Context, _ *adk.TurnLoop[EinoTurnLoopItem, *schema.Message], items []EinoTurnLoopItem) (*adk.GenInputResult[EinoTurnLoopItem, *schema.Message], error) {
|
|
msgs := mergeEinoTurnLoopMessages(items)
|
|
return &adk.GenInputResult[EinoTurnLoopItem, *schema.Message]{
|
|
RunCtx: ctx,
|
|
Input: &adk.AgentInput{
|
|
Messages: msgs,
|
|
EnableStreaming: enableStreaming,
|
|
},
|
|
Consumed: items,
|
|
}, nil
|
|
},
|
|
GenResume: func(ctx context.Context, _ *adk.TurnLoop[EinoTurnLoopItem, *schema.Message], interruptedItems, unhandledItems, newItems []EinoTurnLoopItem) (*adk.GenResumeResult[EinoTurnLoopItem, *schema.Message], error) {
|
|
consumed := make([]EinoTurnLoopItem, 0, len(interruptedItems)+len(newItems))
|
|
consumed = append(consumed, interruptedItems...)
|
|
consumed = append(consumed, newItems...)
|
|
remaining := append([]EinoTurnLoopItem(nil), unhandledItems...)
|
|
return &adk.GenResumeResult[EinoTurnLoopItem, *schema.Message]{
|
|
RunCtx: ctx,
|
|
Consumed: consumed,
|
|
Remaining: remaining,
|
|
}, nil
|
|
},
|
|
PrepareAgent: prepareAgent,
|
|
OnAgentEvents: cfg.OnAgentEvents,
|
|
})
|
|
if len(cfg.InitialMessages) > 0 {
|
|
loop.Push(EinoTurnLoopItem{Kind: "initial", Messages: cloneSchemaMessages(cfg.InitialMessages)})
|
|
}
|
|
return &EinoTurnLoopRuntime{loop: loop, interruptTimeout: timeout}
|
|
}
|
|
|
|
func (r *EinoTurnLoopRuntime) Run(ctx context.Context) {
|
|
if r == nil || r.loop == nil {
|
|
return
|
|
}
|
|
r.loop.Run(ctx)
|
|
}
|
|
|
|
func (r *EinoTurnLoopRuntime) PushInterruptContinue(note string) bool {
|
|
if r == nil || r.loop == nil {
|
|
return false
|
|
}
|
|
item := EinoTurnLoopItem{
|
|
Kind: "interrupt_continue",
|
|
Note: strings.TrimSpace(note),
|
|
Messages: []*schema.Message{schema.UserMessage(formatInterruptContinuePrompt(note))},
|
|
}
|
|
ok, ack := r.loop.Push(item, adk.WithPreemptTimeout[EinoTurnLoopItem, *schema.Message](adk.AnySafePoint, r.interruptTimeout))
|
|
if ack != nil {
|
|
go func() { <-ack }()
|
|
}
|
|
return ok
|
|
}
|
|
|
|
func (r *EinoTurnLoopRuntime) StopImmediate(cause string) {
|
|
if r == nil || r.loop == nil {
|
|
return
|
|
}
|
|
r.loop.Stop(adk.WithImmediate(), adk.WithStopCause(cause))
|
|
}
|
|
|
|
func (r *EinoTurnLoopRuntime) StopWhenIdle() {
|
|
if r == nil || r.loop == nil {
|
|
return
|
|
}
|
|
r.loop.Stop(adk.UntilIdleFor(einoTurnLoopIdleStop))
|
|
}
|
|
|
|
func (r *EinoTurnLoopRuntime) Wait() *adk.TurnLoopExitState[EinoTurnLoopItem, *schema.Message] {
|
|
if r == nil || r.loop == nil {
|
|
return nil
|
|
}
|
|
return r.loop.Wait()
|
|
}
|
|
|
|
func mergeEinoTurnLoopMessages(items []EinoTurnLoopItem) []*schema.Message {
|
|
var msgs []*schema.Message
|
|
for _, item := range items {
|
|
msgs = append(msgs, cloneSchemaMessages(item.Messages)...)
|
|
}
|
|
return msgs
|
|
}
|
|
|
|
func formatInterruptContinuePrompt(note string) string {
|
|
note = strings.TrimSpace(note)
|
|
if note == "" {
|
|
return "用户请求中断当前推理并继续。请基于已经完成的步骤继续,不要重复已完成工具调用。"
|
|
}
|
|
return "用户请求中断当前推理并补充上下文后继续:\n" + note +
|
|
"\n\n请基于已经完成的步骤继续,不要重复已完成工具调用。"
|
|
}
|
|
|
|
func cloneSchemaMessages(in []*schema.Message) []*schema.Message {
|
|
if len(in) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]*schema.Message, 0, len(in))
|
|
for _, msg := range in {
|
|
if msg == nil {
|
|
continue
|
|
}
|
|
cp := *msg
|
|
if len(msg.ToolCalls) > 0 {
|
|
cp.ToolCalls = append([]schema.ToolCall(nil), msg.ToolCalls...)
|
|
}
|
|
if len(msg.MultiContent) > 0 {
|
|
cp.MultiContent = append([]schema.ChatMessagePart(nil), msg.MultiContent...)
|
|
}
|
|
if len(msg.UserInputMultiContent) > 0 {
|
|
cp.UserInputMultiContent = append([]schema.MessageInputPart(nil), msg.UserInputMultiContent...)
|
|
}
|
|
if len(msg.AssistantGenMultiContent) > 0 {
|
|
cp.AssistantGenMultiContent = append([]schema.MessageOutputPart(nil), msg.AssistantGenMultiContent...)
|
|
}
|
|
cp.Extra = cloneAnyMap(msg.Extra)
|
|
out = append(out, &cp)
|
|
}
|
|
return out
|
|
}
|