/* * Copyright 2025 CloudWeGo Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package adk import ( "context" "errors" "io" "strings" "github.com/google/uuid" "github.com/cloudwego/eino/internal" "github.com/cloudwego/eino/schema" ) type AsyncIterator[T any] struct { ch *internal.UnboundedChan[T] } func (ai *AsyncIterator[T]) Next() (T, bool) { return ai.ch.Receive() } type AsyncGenerator[T any] struct { ch *internal.UnboundedChan[T] } func (ag *AsyncGenerator[T]) Send(v T) { ag.ch.Send(v) } func (ag *AsyncGenerator[T]) trySend(v T) bool { return ag.ch.TrySend(v) } func (ag *AsyncGenerator[T]) Close() { ag.ch.Close() } // NewAsyncIteratorPair returns a paired async iterator and generator // that share the same underlying channel. func NewAsyncIteratorPair[T any]() (*AsyncIterator[T], *AsyncGenerator[T]) { ch := internal.NewUnboundedChan[T]() return &AsyncIterator[T]{ch}, &AsyncGenerator[T]{ch} } func copyMap[K comparable, V any](m map[K]V) map[K]V { res := make(map[K]V, len(m)) for k, v := range m { res[k] = v } return res } func cloneSlice[T any](s []T) []T { if s == nil { return nil } res := make([]T, len(s)) copy(res, s) return res } func concatInstructions(instructions ...string) string { var sb strings.Builder sb.WriteString(instructions[0]) for i := 1; i < len(instructions); i++ { sb.WriteString("\n\n") sb.WriteString(instructions[i]) } return sb.String() } // GenTransferMessages generates assistant and tool messages to instruct a // transfer-to-agent tool call targeting the destination agent. // // NOT RECOMMENDED: Agent transfer with full context sharing between agents has not proven // to be more effective empirically. Consider using ChatModelAgent with AgentTool // or DeepAgent instead for most multi-agent scenarios. func GenTransferMessages(_ context.Context, destAgentName string) (Message, Message) { toolCallID := uuid.NewString() tooCall := schema.ToolCall{ID: toolCallID, Function: schema.FunctionCall{Name: TransferToAgentToolName, Arguments: destAgentName}} assistantMessage := schema.AssistantMessage("", []schema.ToolCall{tooCall}) msg := transferToAgentToolOutput(destAgentName) toolMessage := schema.ToolMessage(msg, toolCallID, schema.WithToolName(TransferToAgentToolName)) return assistantMessage, toolMessage } func typedSetAutomaticClose[M MessageType](e *TypedAgentEvent[M]) { if e.Output == nil || e.Output.MessageOutput == nil || !e.Output.MessageOutput.IsStreaming { return } e.Output.MessageOutput.MessageStream.SetAutomaticClose() } // set automatic close for event's message stream func setAutomaticClose(e *AgentEvent) { typedSetAutomaticClose(e) } // getMessageFromWrappedEvent extracts the message from an AgentEvent. // If the stream contains an error chunk, this function returns (nil, err) and // sets StreamErr to prevent re-consumption. The nil message ensures that // failed stream responses are not included in subsequent agents' context windows. func getMessageFromTypedWrappedEvent[M MessageType](e *typedAgentEventWrapper[M]) (M, error) { var zero M if e.event.Output == nil || e.event.Output.MessageOutput == nil { return zero, nil } if !e.event.Output.MessageOutput.IsStreaming { return e.event.Output.MessageOutput.Message, nil } if e.StreamErr != nil { return zero, e.StreamErr } if !isNilMessage(e.concatenatedMessage) { return e.concatenatedMessage, nil } e.consumeStream() if e.StreamErr != nil { return zero, e.StreamErr } return e.concatenatedMessage, nil } func getMessageFromWrappedEvent(e *agentEventWrapper) (Message, error) { if e.AgentEvent.Output == nil || e.AgentEvent.Output.MessageOutput == nil { return nil, nil } if !e.AgentEvent.Output.MessageOutput.IsStreaming { return e.AgentEvent.Output.MessageOutput.Message, nil } if e.StreamErr != nil { return nil, e.StreamErr } if e.concatenatedMessage != nil { return e.concatenatedMessage, nil } e.consumeStream() if e.StreamErr != nil { return nil, e.StreamErr } return e.concatenatedMessage, nil } // consumeStream drains the message stream, setting concatenatedMessage on // success or StreamErr on failure. The stream is always replaced with an // error-free, materialized version safe for gob encoding. // Must be called at most once (guarded by callers checking concatenatedMessage/StreamErr). func (e *agentEventWrapper) consumeStream() { e.mu.Lock() defer e.mu.Unlock() if e.concatenatedMessage != nil { return } s := e.AgentEvent.Output.MessageOutput.MessageStream var msgs []Message defer s.Close() for { msg, err := s.Recv() if err != nil { if err == io.EOF { break } e.StreamErr = err e.AgentEvent.Output.MessageOutput.MessageStream = schema.StreamReaderFromArray(msgs) return } msgs = append(msgs, msg) } if len(msgs) == 0 { e.StreamErr = errors.New("no messages in MessageVariant.MessageStream") // Defensively replace the stream. The defer s.Close() above already // ensures subsequent Recv() returns io.EOF, but we replace it anyway // to make the invariant explicit: after consumeStream, MessageStream // is always safe for MessageVariant.GobEncode to consume. e.AgentEvent.Output.MessageOutput.MessageStream = schema.StreamReaderFromArray(msgs) return } if len(msgs) == 1 { e.concatenatedMessage = msgs[0] } else { var err error e.concatenatedMessage, err = schema.ConcatMessages(msgs) if err != nil { e.StreamErr = err e.AgentEvent.Output.MessageOutput.MessageStream = schema.StreamReaderFromArray(msgs) return } } e.AgentEvent.Output.MessageOutput.MessageStream = schema.StreamReaderFromArray([]Message{e.concatenatedMessage}) } // copyTypedAgentEvent copies a TypedAgentEvent. // If the MessageVariant is streaming, the MessageStream will be copied. // RunPath will be deep copied. // The result of Copy will be a new TypedAgentEvent that is: // - safe to set fields of TypedAgentEvent // - safe to extend RunPath // - safe to receive from MessageStream // NOTE: even if the event is copied, it's still not recommended to modify // the Message itself or Chunks of the MessageStream, as they are not copied. // NOTE: if you have CustomizedOutput or CustomizedAction, they are NOT copied. func copyTypedAgentEvent[M MessageType](ae *TypedAgentEvent[M]) *TypedAgentEvent[M] { rp := make([]RunStep, len(ae.RunPath)) copy(rp, ae.RunPath) copied := &TypedAgentEvent[M]{ AgentName: ae.AgentName, RunPath: rp, Action: ae.Action, Err: ae.Err, } if ae.Output == nil { return copied } copied.Output = &TypedAgentOutput[M]{ CustomizedOutput: ae.Output.CustomizedOutput, } mv := ae.Output.MessageOutput if mv == nil { return copied } copied.Output.MessageOutput = &TypedMessageVariant[M]{ IsStreaming: mv.IsStreaming, Role: mv.Role, AgenticRole: mv.AgenticRole, ToolName: mv.ToolName, } if mv.IsStreaming { sts := ae.Output.MessageOutput.MessageStream.Copy(2) mv.MessageStream = sts[0] copied.Output.MessageOutput.MessageStream = sts[1] } else { copied.Output.MessageOutput.Message = mv.Message } return copied } // TypedGetMessage extracts the message from a TypedAgentEvent, concatenating a stream if present. func TypedGetMessage[M MessageType](e *TypedAgentEvent[M]) (M, *TypedAgentEvent[M], error) { var zero M if e.Output == nil || e.Output.MessageOutput == nil { return zero, e, nil } msgOutput := e.Output.MessageOutput if msgOutput.IsStreaming { ss := msgOutput.MessageStream.Copy(2) e.Output.MessageOutput.MessageStream = ss[0] msg, err := concatMessageStream(ss[1]) return msg, e, err } return msgOutput.Message, e, nil } // GetMessage extracts the Message from an AgentEvent. For streaming output, // it duplicates the stream and concatenates it into a single Message. func GetMessage(e *AgentEvent) (Message, *AgentEvent, error) { return TypedGetMessage(e) } func typedErrorIter[M MessageType](err error) *AsyncIterator[*TypedAgentEvent[M]] { iterator, generator := NewAsyncIteratorPair[*TypedAgentEvent[M]]() generator.Send(&TypedAgentEvent[M]{Err: err}) generator.Close() return iterator } func genErrorIter(err error) *AsyncIterator[*AgentEvent] { return typedErrorIter[*schema.Message](err) }