diff --git a/agent/assistant/agent.go b/agent/assistant/agent.go index 0e32c212..85417c31 100644 --- a/agent/assistant/agent.go +++ b/agent/assistant/agent.go @@ -1,9 +1,14 @@ package assistant -import "github.com/yaoapp/yao/agent/context" +import ( + "github.com/yaoapp/yao/agent/context" + "github.com/yaoapp/yao/agent/llm" +) // Stream stream the agent -func (ast *Assistant) Stream(ctx *context.Context, messages []context.Message, handler context.StreamFunc) error { +func (ast *Assistant) Stream(ctx *context.Context, inputMessages []context.Message, handler context.StreamFunc) (*context.Response, error) { + + var err error // Initialize stack and auto-handle completion/failure/restore _, traceID, done := context.EnterStack(ctx, ast.ID, ctx.Referer) @@ -11,23 +16,85 @@ func (ast *Assistant) Stream(ctx *context.Context, messages []context.Message, h _ = traceID // traceID is available for trace logging + // Full input messages with chat history + fullMessages, err := ast.WithHistory(ctx, inputMessages) + if err != nil { + return nil, err + } + // Request Create hook ( Optional ) + var createResponse *context.ResponseHookCreate + if ast.Script != nil { + var err error + createResponse, err = ast.Script.Create(ctx, fullMessages) + if err != nil { + return nil, err + } + } + _ = createResponse // createResponse is available for further processing + + var completionOptions *llm.CompletionOptions // default is nil // LLM Call Stream ( Optional ) + var completionMessages []context.Message + var completionResponse *context.ResponseCompletion + if ast.Prompts != nil || ast.MCP != nil { + llm, err := llm.New(ast.GetConnector(ctx)) + if err != nil { + return nil, err + } + + // Build the LLM request + completionMessages, completionOptions, err = ast.BuildLLMRequest(ctx, inputMessages, createResponse) + if err != nil { + return nil, err + } + + // Call the LLM Completion Stream + completionResponse, err = llm.Stream(ctx, completionMessages, completionOptions, handler) + if err != nil { + return nil, err + } + + } + + // Request MCP hook ( Optional ) + var mcpResponse *context.ResponseHookMCP + if ast.MCP != nil { + _ = mcpResponse // mcpResponse is available for further processing + + // MCP Execution Loop + } // Request Done hook ( Optional ) + var doneResponse *context.ResponseHookDone + if ast.Script != nil { + var err error + doneResponse, err = ast.Script.Done(ctx, fullMessages, completionResponse, mcpResponse) + if err != nil { + return nil, err + } + } - return nil + _ = doneResponse // doneResponse is available for further processing + + return &context.Response{Create: createResponse, Done: doneResponse, Completion: completionResponse}, nil } -// Run run the agent -func (ast *Assistant) Run(ctx *context.Context, messages []context.Message) (*context.Response, error) { - - // Initialize stack and auto-handle completion/failure/restore - _, traceID, done := context.EnterStack(ctx, ast.ID, ctx.Referer) - defer done() - - _ = traceID // traceID is available for trace logging - - return &context.Response{}, nil +// GetConnector get the connector from the context +func (ast *Assistant) GetConnector(ctx *context.Context) string { + if ctx.Connector != "" { + return ctx.Connector + } + return ast.Connector +} + +// BuildLLMRequest build the LLM request +func (ast *Assistant) BuildLLMRequest(ctx *context.Context, messages []context.Message, createResponse *context.ResponseHookCreate) ([]context.Message, *llm.CompletionOptions, error) { + return messages, nil, nil +} + +// WithHistory with the history messages +func (ast *Assistant) WithHistory(ctx *context.Context, messages []context.Message) ([]context.Message, error) { + return messages, nil } diff --git a/agent/assistant/hook/create.go b/agent/assistant/hook/create.go new file mode 100644 index 00000000..1d5fec4f --- /dev/null +++ b/agent/assistant/hook/create.go @@ -0,0 +1,8 @@ +package hook + +import "github.com/yaoapp/yao/agent/context" + +// Create create a new assistant +func (s *Script) Create(ctx *context.Context, messages []context.Message) (*context.ResponseHookCreate, error) { + return &context.ResponseHookCreate{}, nil +} diff --git a/agent/assistant/hook/done.go b/agent/assistant/hook/done.go new file mode 100644 index 00000000..69e990a9 --- /dev/null +++ b/agent/assistant/hook/done.go @@ -0,0 +1,10 @@ +package hook + +import ( + "github.com/yaoapp/yao/agent/context" +) + +// Done done hook +func (s *Script) Done(ctx *context.Context, inputMessages []context.Message, completionResponse *context.ResponseCompletion, mcpResponse *context.ResponseHookMCP) (*context.ResponseHookDone, error) { + return &context.ResponseHookDone{}, nil +} diff --git a/agent/assistant/hook/failback.go b/agent/assistant/hook/failback.go new file mode 100644 index 00000000..0b345cf4 --- /dev/null +++ b/agent/assistant/hook/failback.go @@ -0,0 +1,8 @@ +package hook + +import "github.com/yaoapp/yao/agent/context" + +// Failback failback hook +func (s *Script) Failback(ctx *context.Context, inputMessages []context.Message, completionResponse *context.ResponseCompletion) (*context.ResponseHookFailback, error) { + return &context.ResponseHookFailback{}, nil +} diff --git a/agent/assistant/hook/hook.go b/agent/assistant/hook/hook.go new file mode 100644 index 00000000..67dcf257 --- /dev/null +++ b/agent/assistant/hook/hook.go @@ -0,0 +1 @@ +package hook diff --git a/agent/assistant/hook/mcp.go b/agent/assistant/hook/mcp.go new file mode 100644 index 00000000..0e0ba6cc --- /dev/null +++ b/agent/assistant/hook/mcp.go @@ -0,0 +1,8 @@ +package hook + +import "github.com/yaoapp/yao/agent/context" + +// MCP MCP hook +func (s *Script) MCP(ctx *context.Context, messages []context.Message) (*context.ResponseHookMCP, error) { + return &context.ResponseHookMCP{}, nil +} diff --git a/agent/assistant/hook/script.go b/agent/assistant/hook/script.go new file mode 100644 index 00000000..67dcf257 --- /dev/null +++ b/agent/assistant/hook/script.go @@ -0,0 +1 @@ +package hook diff --git a/agent/assistant/hook/types.go b/agent/assistant/hook/types.go new file mode 100644 index 00000000..72cb34b2 --- /dev/null +++ b/agent/assistant/hook/types.go @@ -0,0 +1,10 @@ +package hook + +import ( + v8 "github.com/yaoapp/gou/runtime/v8" +) + +// Script the script hook align +type Script struct { + *v8.Script +} diff --git a/agent/assistant/hooks/hooks.go b/agent/assistant/hooks/hooks.go deleted file mode 100644 index 6de678e5..00000000 --- a/agent/assistant/hooks/hooks.go +++ /dev/null @@ -1 +0,0 @@ -package hooks diff --git a/agent/assistant/load.go b/agent/assistant/load.go index a6ed26be..236ad3ab 100644 --- a/agent/assistant/load.go +++ b/agent/assistant/load.go @@ -13,6 +13,7 @@ import ( "github.com/yaoapp/gou/application" "github.com/yaoapp/gou/fs" v8 "github.com/yaoapp/gou/runtime/v8" + "github.com/yaoapp/yao/agent/assistant/hook" "github.com/yaoapp/yao/agent/i18n" store "github.com/yaoapp/yao/agent/store/types" agentvision "github.com/yaoapp/yao/agent/vision" @@ -106,9 +107,7 @@ func LoadBuiltIn() error { loaded.Put(assistant) // Remove the built-in assistant from the store - if _, ok := deletedBuiltIn[assistant.ID]; ok { - delete(deletedBuiltIn, assistant.ID) - } + delete(deletedBuiltIn, assistant.ID) } // Remove deleted built-in assistants @@ -290,16 +289,16 @@ func LoadPath(path string) (*Assistant, error) { data["updated_at"] = max(updatedAt, ts) } - // load tools - toolsfile := filepath.Join(path, "tools.yao") - if has, _ := app.Exists(toolsfile); has { - tools, ts, err := loadTools(toolsfile) - if err != nil { - return nil, err - } - data["tools"] = tools - updatedAt = max(updatedAt, ts) - } + // load tools, deprecated, use mcp instead + // toolsfile := filepath.Join(path, "tools.yao") + // if has, _ := app.Exists(toolsfile); has { + // tools, ts, err := loadTools(toolsfile) + // if err != nil { + // return nil, err + // } + // data["tools"] = tools + // updatedAt = max(updatedAt, ts) + // } // i18ns locales, err := i18n.GetLocales(path) @@ -430,6 +429,9 @@ func loadMap(data map[string]interface{}) (*Assistant, error) { } assistant.Tags = tags + case string: + assistant.Tags = []string{vv} + case interface{}: raw, err := jsoniter.Marshal(vv) if err != nil { @@ -442,8 +444,6 @@ func loadMap(data map[string]interface{}) (*Assistant, error) { } assistant.Tags = tags - case string: - assistant.Tags = []string{vv} } } @@ -571,9 +571,11 @@ func loadMap(data map[string]interface{}) (*Assistant, error) { if err != nil { return nil, err } - assistant.Script = script - case *v8.Script: + assistant.Script = &hook.Script{Script: script} + case *hook.Script: assistant.Script = v + case *v8.Script: + assistant.Script = &hook.Script{Script: v} } } @@ -641,7 +643,7 @@ func loadPrompts(file string, root string) (string, int64, error) { return string(prompts), ts.UnixNano(), nil } -func loadScript(file string, root string) (*v8.Script, int64, error) { +func loadScript(file string, root string) (*hook.Script, int64, error) { app, err := fs.Get("app") if err != nil { @@ -658,7 +660,7 @@ func loadScript(file string, root string) (*v8.Script, int64, error) { return nil, 0, err } - return script, ts.UnixNano(), nil + return &hook.Script{Script: script}, ts.UnixNano(), nil } func loadScriptSource(source string, file string) (*v8.Script, error) { diff --git a/agent/assistant/types.go b/agent/assistant/types.go index fc188482..46f0962d 100644 --- a/agent/assistant/types.go +++ b/agent/assistant/types.go @@ -5,7 +5,7 @@ import ( "io" "github.com/gin-gonic/gin" - v8 "github.com/yaoapp/gou/runtime/v8" + "github.com/yaoapp/yao/agent/assistant/hook" chatctx "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/message" store "github.com/yaoapp/yao/agent/store/types" @@ -96,16 +96,16 @@ type QueryParam struct { type Assistant struct { store.AssistantModel Search *SearchOption `json:"search,omitempty" yaml:"search,omitempty"` // Whether this assistant supports search - Script *v8.Script `json:"-" yaml:"-"` // Assistant Script + Script *hook.Script `json:"-" yaml:"-"` // Assistant Script // Internal // =============================== - openai *api.OpenAI // OpenAI API - search bool // Whether this assistant supports search - vision bool // Whether this assistant supports vision - toolCalls bool // Whether this assistant supports tool_calls - initHook bool // Whether this assistant has an init hook - runtimeTools []Tool // Converted tools for business logic (OpenAI format) + openai *api.OpenAI // OpenAI API + search bool // Whether this assistant supports search + vision bool // Whether this assistant supports vision + // toolCalls bool // Whether this assistant supports tool_calls + initHook bool // Whether this assistant has an init hook + runtimeTools []Tool // Converted tools for business logic (OpenAI format) } // ConnectorSetting the connector setting diff --git a/agent/context/types.go b/agent/context/types.go index 74068f14..c443c1f2 100644 --- a/agent/context/types.go +++ b/agent/context/types.go @@ -131,6 +131,8 @@ type Context struct { ChatID string `json:"chat_id,omitempty"` // Chat ID, use to select chat AssistantID string `json:"assistant_id,omitempty"` // Assistant ID, use to select assistant Sid string `json:"sid" yaml:"-"` // Session ID (Deprecated, use Authorized instead) + Connector string `json:"connector,omitempty"` // Connector, use to select the connector of the LLM Model, Default is Assistant.Connector + Search *bool `json:"search,omitempty"` // Search mode, default is true // Arguments for call Args []interface{} `json:"args,omitempty"` // Arguments for call, it will be used to pass data to the call @@ -181,7 +183,28 @@ type Stack struct { // Response the response // 100% compatible with the OpenAI API -type Response struct{} +type Response struct { + Create *ResponseHookCreate `json:"create,omitempty"` + MCP *ResponseHookMCP `json:"mcp,omitempty"` + Done *ResponseHookDone `json:"done,omitempty"` + Failback *ResponseHookFailback `json:"failback,omitempty"` + Completion *ResponseCompletion `json:"completion,omitempty"` +} + +// ResponseHookCreate the response of the create hook +type ResponseHookCreate struct{} + +// ResponseHookDone the response of the done hook +type ResponseHookDone struct{} + +// ResponseHookMCP the response of the mcp hook +type ResponseHookMCP struct{} + +// ResponseHookFailback the response of the failback hook +type ResponseHookFailback struct{} + +// ResponseCompletion the response of the completion +type ResponseCompletion struct{} // Message Structure ( OpenAI Chat Completion Input Message Structure, https://platform.openai.com/docs/api-reference/chat/create#chat/create-messages ) // =============================== diff --git a/agent/llm/interfaces.go b/agent/llm/interfaces.go new file mode 100644 index 00000000..53562b40 --- /dev/null +++ b/agent/llm/interfaces.go @@ -0,0 +1,9 @@ +package llm + +import "github.com/yaoapp/yao/agent/context" + +// LLM the LLM interface +type LLM interface { + Stream(ctx *context.Context, messages []context.Message, options *CompletionOptions, handler context.StreamFunc) (*context.ResponseCompletion, error) + Post(ctx *context.Context, messages []context.Message, options *CompletionOptions) (*context.ResponseCompletion, error) +} diff --git a/agent/llm/llm.go b/agent/llm/llm.go index 006c3ded..4e4c1be3 100644 --- a/agent/llm/llm.go +++ b/agent/llm/llm.go @@ -1 +1,6 @@ package llm + +// New create a new LLM instance +func New(connector string) (LLM, error) { + return nil, nil +} diff --git a/agent/llm/types.go b/agent/llm/types.go new file mode 100644 index 00000000..764efb7a --- /dev/null +++ b/agent/llm/types.go @@ -0,0 +1,4 @@ +package llm + +// CompletionOptions the completion request +type CompletionOptions struct{}