yao/agent/context/output.go
Max ab240443f8 Add message metadata handling and new context methods for message operations
- Introduced recordMessageMetadata and getMessageMetadata methods to manage metadata for sent messages, enabling BlockID and ThreadID inheritance in delta operations.
- Implemented new methods (Append, Merge, Set) in the context to enhance message management capabilities, allowing for more flexible message updates.
- Updated the Send method to automatically manage BlockID and ThreadID for delta operations, improving message handling consistency.
- Enhanced JSAPI documentation to reflect new methods and usage patterns, improving developer experience and clarity.
2025-11-30 10:39:51 +08:00

115 lines
3.3 KiB
Go

package context
import (
"github.com/yaoapp/yao/agent/output"
"github.com/yaoapp/yao/agent/output/message"
)
// Send sends a message via the output module
// Automatically manages BlockID, ThreadID, and metadata for delta operations
// - For delta operations: inherits BlockID and ThreadID from original message
// - For new messages: auto-generates BlockID if not specified, sets ThreadID from Stack
// - Records metadata for all sent messages to enable delta inheritance
func (ctx *Context) Send(msg *message.Message) error {
output, err := ctx.getOutput()
if err != nil {
return err
}
// === Delta operations: Auto-inherit BlockID and ThreadID ===
if msg.Delta && msg.MessageID != "" {
if metadata := ctx.getMessageMetadata(msg.MessageID); metadata != nil {
// Inherit BlockID if not specified
if msg.BlockID == "" {
msg.BlockID = metadata.BlockID
}
// Inherit ThreadID if not specified
if msg.ThreadID == "" {
msg.ThreadID = metadata.ThreadID
}
}
}
// === Non-delta operations: Auto-set fields ===
if !msg.Delta {
// Auto-set ThreadID for non-root Stack (nested agent calls)
if msg.ThreadID == "" && ctx.Stack != nil && !ctx.Stack.IsRoot() {
msg.ThreadID = ctx.Stack.ID
}
// BlockID is NOT auto-generated by default (only manually specified in special cases)
// Example: Send a web card after LLM output, group them in the same Block
// Developers can specify via ctx.Send(message, blockId) or message.block_id
}
// === Record metadata for subsequent delta operations ===
ctx.recordMessageMetadata(msg)
// === Actually send the message ===
return output.Send(msg)
}
// SendGroup sends a group of messages via the output module
// Deprecated: This method is deprecated and will be removed in future versions
func (ctx *Context) SendGroup(group *message.Group) error {
output, err := ctx.getOutput()
if err != nil {
return err
}
return output.SendGroup(group)
}
// Flush flushes the output writer
func (ctx *Context) Flush() error {
output, err := ctx.getOutput()
if err != nil {
return err
}
return output.Flush()
}
// CloseOutput closes the output writer
func (ctx *Context) CloseOutput() error {
output, err := ctx.getOutput()
if err != nil {
return err
}
return output.Close()
}
// getOutput gets the output writer for the context
func (ctx *Context) getOutput() (*output.Output, error) {
if ctx.output != nil {
return ctx.output, nil
}
trace, _ := ctx.Trace()
var options message.Options = message.Options{
BaseURL: "/",
Writer: ctx.Writer,
Trace: trace,
Locale: ctx.Locale,
Accept: string(ctx.Accept),
}
// Convert ModelCapabilities to message.ModelCapabilities
if ctx.Capabilities != nil {
options.Capabilities = &message.ModelCapabilities{
Vision: ctx.Capabilities.Vision,
ToolCalls: ctx.Capabilities.ToolCalls,
Audio: ctx.Capabilities.Audio,
Reasoning: ctx.Capabilities.Reasoning,
Streaming: ctx.Capabilities.Streaming,
JSON: ctx.Capabilities.JSON,
Multimodal: ctx.Capabilities.Multimodal,
TemperatureAdjustable: ctx.Capabilities.TemperatureAdjustable,
}
}
var err error
ctx.output, err = output.NewOutput(options)
if err != nil {
return nil, err
}
return ctx.output, nil
}