268 lines
9.6 KiB
Go
268 lines
9.6 KiB
Go
/*
|
|
* 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 deep provides a prebuilt agent with deep task orchestration.
|
|
package deep
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/bytedance/sonic"
|
|
|
|
"github.com/cloudwego/eino/adk"
|
|
"github.com/cloudwego/eino/adk/filesystem"
|
|
"github.com/cloudwego/eino/adk/internal"
|
|
filesystem2 "github.com/cloudwego/eino/adk/middlewares/filesystem"
|
|
"github.com/cloudwego/eino/components/model"
|
|
"github.com/cloudwego/eino/components/tool/utils"
|
|
"github.com/cloudwego/eino/schema"
|
|
)
|
|
|
|
func init() {
|
|
schema.RegisterName[TODO]("_eino_adk_prebuilt_deep_todo")
|
|
schema.RegisterName[[]TODO]("_eino_adk_prebuilt_deep_todo_slice")
|
|
}
|
|
|
|
// TypedConfig defines the configuration for creating a DeepAgent parameterized by message type.
|
|
// An Agentic DeepAgent (M = *schema.AgenticMessage) only supports Agentic sub-agents,
|
|
// and a standard DeepAgent (M = *schema.Message) only supports standard sub-agents.
|
|
// This is enforced by the type system through the SubAgents field.
|
|
type TypedConfig[M adk.MessageType] struct {
|
|
// Name is the identifier for the Deep agent.
|
|
Name string
|
|
// Description provides a brief explanation of the agent's purpose.
|
|
Description string
|
|
|
|
// ChatModel is the model used by DeepAgent for reasoning and task execution.
|
|
// If the agent uses any tools, this model must support the model.WithTools call option,
|
|
// as that's how the agent configures the model with tool information.
|
|
ChatModel model.BaseModel[M]
|
|
// Instruction contains the system prompt that guides the agent's behavior.
|
|
// When empty, a built-in default system prompt will be used, which includes general assistant
|
|
// behavior guidelines, security policies, coding style guidelines, and tool usage policies.
|
|
Instruction string
|
|
// SubAgents are specialized agents that can be invoked by the agent.
|
|
// For M = *schema.AgenticMessage, only agentic sub-agents are accepted.
|
|
SubAgents []adk.TypedAgent[M]
|
|
// ToolsConfig provides the tools and tool-calling configurations available for the agent to invoke.
|
|
ToolsConfig adk.ToolsConfig
|
|
// MaxIteration limits the maximum number of reasoning iterations the agent can perform.
|
|
MaxIteration int
|
|
|
|
// Backend provides filesystem operations used by tools and offloading.
|
|
// If set, filesystem tools (read_file, write_file, edit_file, glob, grep) will be registered.
|
|
// Optional.
|
|
Backend filesystem.Backend
|
|
// Shell provides shell command execution capability.
|
|
// If set, an execute tool will be registered to support shell command execution.
|
|
// Optional. Mutually exclusive with StreamingShell.
|
|
Shell filesystem.Shell
|
|
// StreamingShell provides streaming shell command execution capability.
|
|
// If set, a streaming execute tool will be registered to support streaming shell command execution.
|
|
// Optional. Mutually exclusive with Shell.
|
|
StreamingShell filesystem.StreamingShell
|
|
|
|
// WithoutWriteTodos disables the built-in write_todos tool when set to true.
|
|
WithoutWriteTodos bool
|
|
// WithoutGeneralSubAgent disables the general-purpose subagent when set to true.
|
|
WithoutGeneralSubAgent bool
|
|
// TaskToolDescriptionGenerator allows customizing the description for the task tool.
|
|
// If provided, this function generates the tool description based on available subagents.
|
|
TaskToolDescriptionGenerator func(ctx context.Context, availableAgents []adk.TypedAgent[M]) (string, error)
|
|
|
|
Middlewares []adk.AgentMiddleware
|
|
|
|
// Handlers configures interface-based handlers for extending agent behavior.
|
|
// Unlike Middlewares (struct-based), Handlers allow users to:
|
|
// - Add custom methods to their handler implementations
|
|
// - Return modified context from handler methods
|
|
// - Centralize configuration in struct fields instead of closures
|
|
//
|
|
// Handlers are processed after Middlewares, in registration order.
|
|
// See adk.ChatModelAgentMiddleware documentation for when to use Handlers vs Middlewares.
|
|
Handlers []adk.TypedChatModelAgentMiddleware[M]
|
|
|
|
ModelRetryConfig *adk.TypedModelRetryConfig[M]
|
|
// ModelFailoverConfig configures failover behavior for the ChatModel.
|
|
// When set, the agent will automatically fail over to alternative models on errors.
|
|
// This config is also propagated to the general sub-agent.
|
|
ModelFailoverConfig *adk.ModelFailoverConfig[M]
|
|
|
|
// OutputKey stores the agent's response in the session.
|
|
// Optional. When set, stores output via AddSessionValue(ctx, outputKey, msg.Content).
|
|
OutputKey string
|
|
}
|
|
|
|
// Config defines the configuration for creating a standard DeepAgent.
|
|
type Config = TypedConfig[*schema.Message]
|
|
|
|
// NewTyped creates a new typed Deep agent instance with the provided configuration.
|
|
// This function initializes built-in tools, creates a task tool for subagent orchestration,
|
|
// and returns a fully configured TypedChatModelAgent ready for execution.
|
|
func NewTyped[M adk.MessageType](ctx context.Context, cfg *TypedConfig[M]) (adk.TypedResumableAgent[M], error) {
|
|
handlers, err := buildTypedBuiltinAgentMiddlewares(ctx, cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
instruction := cfg.Instruction
|
|
if len(instruction) == 0 {
|
|
instruction = internal.SelectPrompt(internal.I18nPrompts{
|
|
English: baseAgentInstruction,
|
|
Chinese: baseAgentInstructionChinese,
|
|
})
|
|
}
|
|
|
|
if !cfg.WithoutGeneralSubAgent || len(cfg.SubAgents) > 0 {
|
|
tt, err := typedTaskToolMiddleware(
|
|
ctx,
|
|
cfg.TaskToolDescriptionGenerator,
|
|
cfg.SubAgents,
|
|
|
|
cfg.WithoutGeneralSubAgent,
|
|
cfg.ChatModel,
|
|
instruction,
|
|
cfg.ToolsConfig,
|
|
cfg.MaxIteration,
|
|
cfg.Middlewares,
|
|
append(handlers, cfg.Handlers...),
|
|
cfg.ModelFailoverConfig,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to new task tool: %w", err)
|
|
}
|
|
handlers = append(handlers, tt)
|
|
}
|
|
|
|
return adk.NewTypedChatModelAgent(ctx, &adk.TypedChatModelAgentConfig[M]{
|
|
Name: cfg.Name,
|
|
Description: cfg.Description,
|
|
Instruction: instruction,
|
|
Model: cfg.ChatModel,
|
|
ToolsConfig: cfg.ToolsConfig,
|
|
MaxIterations: cfg.MaxIteration,
|
|
Middlewares: cfg.Middlewares,
|
|
Handlers: append(handlers, cfg.Handlers...),
|
|
|
|
GenModelInput: typedGenModelInput[M],
|
|
ModelRetryConfig: cfg.ModelRetryConfig,
|
|
ModelFailoverConfig: cfg.ModelFailoverConfig,
|
|
OutputKey: cfg.OutputKey,
|
|
})
|
|
}
|
|
|
|
// New creates a new Deep agent instance with the provided configuration.
|
|
// This function initializes built-in tools, creates a task tool for subagent orchestration,
|
|
// and returns a fully configured ChatModelAgent ready for execution.
|
|
func New(ctx context.Context, cfg *Config) (adk.ResumableAgent, error) {
|
|
return NewTyped(ctx, cfg)
|
|
}
|
|
|
|
func typedGenModelInput[M adk.MessageType](_ context.Context, instruction string, input *adk.TypedAgentInput[M]) ([]M, error) {
|
|
var zero M
|
|
switch any(zero).(type) {
|
|
case *schema.Message:
|
|
msgs := make([]*schema.Message, 0, len(input.Messages)+1)
|
|
if instruction != "" {
|
|
msgs = append(msgs, schema.SystemMessage(instruction))
|
|
}
|
|
// Type assertion is safe here because M = *schema.Message.
|
|
for _, m := range input.Messages {
|
|
msgs = append(msgs, any(m).(*schema.Message))
|
|
}
|
|
result := make([]M, len(msgs))
|
|
for i, m := range msgs {
|
|
result[i] = any(m).(M)
|
|
}
|
|
return result, nil
|
|
case *schema.AgenticMessage:
|
|
msgs := make([]*schema.AgenticMessage, 0, len(input.Messages)+1)
|
|
if instruction != "" {
|
|
msgs = append(msgs, schema.SystemAgenticMessage(instruction))
|
|
}
|
|
for _, m := range input.Messages {
|
|
msgs = append(msgs, any(m).(*schema.AgenticMessage))
|
|
}
|
|
result := make([]M, len(msgs))
|
|
for i, m := range msgs {
|
|
result[i] = any(m).(M)
|
|
}
|
|
return result, nil
|
|
}
|
|
panic("unreachable")
|
|
}
|
|
|
|
func buildTypedBuiltinAgentMiddlewares[M adk.MessageType](ctx context.Context, cfg *TypedConfig[M]) ([]adk.TypedChatModelAgentMiddleware[M], error) {
|
|
var ms []adk.TypedChatModelAgentMiddleware[M]
|
|
if !cfg.WithoutWriteTodos {
|
|
t, err := typedNewWriteTodos[M]()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ms = append(ms, t)
|
|
}
|
|
|
|
if cfg.Backend != nil || cfg.Shell != nil || cfg.StreamingShell != nil {
|
|
fm, err := filesystem2.NewTyped[M](ctx, &filesystem2.MiddlewareConfig{
|
|
Backend: cfg.Backend,
|
|
Shell: cfg.Shell,
|
|
StreamingShell: cfg.StreamingShell,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ms = append(ms, fm)
|
|
}
|
|
|
|
return ms, nil
|
|
}
|
|
|
|
type TODO struct {
|
|
Content string `json:"content"`
|
|
ActiveForm string `json:"activeForm"`
|
|
Status string `json:"status" jsonschema:"enum=pending,enum=in_progress,enum=completed"`
|
|
}
|
|
|
|
type writeTodosArguments struct {
|
|
Todos []TODO `json:"todos"`
|
|
}
|
|
|
|
func typedNewWriteTodos[M adk.MessageType]() (adk.TypedChatModelAgentMiddleware[M], error) {
|
|
toolDesc := internal.SelectPrompt(internal.I18nPrompts{
|
|
English: writeTodosToolDescription,
|
|
Chinese: writeTodosToolDescriptionChinese,
|
|
})
|
|
resultMsg := internal.SelectPrompt(internal.I18nPrompts{
|
|
English: "Updated todo list to %s",
|
|
Chinese: "已更新待办列表为 %s",
|
|
})
|
|
|
|
t, err := utils.InferTool("write_todos", toolDesc, func(ctx context.Context, input writeTodosArguments) (output string, err error) {
|
|
adk.AddSessionValue(ctx, SessionKeyTodos, input.Todos)
|
|
todos, err := sonic.MarshalString(input.Todos)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return fmt.Sprintf(resultMsg, todos), nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return typedBuildAppendPromptTool[M]("", t), nil
|
|
}
|