From 1f9810c26cfdbf5d330148b40635b04bcf4fc94b Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 23 Jan 2025 16:38:13 +0800 Subject: [PATCH] Implement assistant call functionality in Neo API - Added a new endpoint for executing assistant calls via POST request, enhancing the API's capabilities. - Introduced the handleAssistantCall method to process incoming requests, validate parameters, and execute the assistant's script. - Updated the Assistant interface to include the Call method, allowing for dynamic method invocation based on the provided payload. - Enhanced error handling for missing parameters and invalid requests, improving the robustness of the assistant's API interactions. These changes significantly improve the Neo API assistant's functionality, enabling more interactive and dynamic assistant capabilities. --- neo/api.go | 49 ++++++++++++++++++++++++++++++++++++++++++ neo/assistant/api.go | 19 ++++++++++++++++ neo/assistant/types.go | 8 +++++++ 3 files changed, 76 insertions(+) diff --git a/neo/api.go b/neo/api.go index a67ad151..4d9de7db 100644 --- a/neo/api.go +++ b/neo/api.go @@ -15,6 +15,7 @@ import ( "github.com/yaoapp/gou/connector" "github.com/yaoapp/gou/process" "github.com/yaoapp/yao/helper" + "github.com/yaoapp/yao/neo/assistant" chatctx "github.com/yaoapp/yao/neo/context" "github.com/yaoapp/yao/neo/message" "github.com/yaoapp/yao/neo/store" @@ -44,7 +45,9 @@ func (neo *DSL) API(router *gin.Engine, path string) error { router.OPTIONS(path+"/dangerous/clear_chats", neo.optionsHandler) router.OPTIONS(path+"/assistants", neo.optionsHandler) router.OPTIONS(path+"/assistants/:id", neo.optionsHandler) + router.OPTIONS(path+"/assistants/:id/call", neo.optionsHandler) + // Chat endpoint // Chat endpoint // Example: // curl -X GET 'http://localhost:5099/api/__yao/neo?content=Hello&chat_id=chat_123&context=previous_context&token=xxx' @@ -71,6 +74,12 @@ func (neo *DSL) API(router *gin.Engine, path string) error { // curl -X GET 'http://localhost:5099/api/__yao/neo/assistants/assistant_123?token=xxx' router.GET(path+"/assistants/:id", append(middlewares, neo.handleAssistantDetail)...) + // Execute assistant API example: + // curl -X POST 'http://localhost:5099/api/__yao/neo/assistants/assistant_123/api' \ + // -H 'Content-Type: application/json' \ + // -d '{"name": "Test", "payload": {"name": "yao", "age": 18}}' + router.POST(path+"/assistants/:id/call", append(middlewares, neo.handleAssistantCall)...) + // Create/Update assistant example: // curl -X POST 'http://localhost:5099/api/__yao/neo/assistants' \ // -H 'Content-Type: application/json' \ @@ -938,6 +947,46 @@ func parseBoolValue(value string) *bool { } } +// handleAssistantAPI handles the assistant API +func (neo *DSL) handleAssistantCall(c *gin.Context) { + assistantID := c.Param("id") + if assistantID == "" { + c.JSON(400, gin.H{"message": "assistant id is required", "code": 400}) + c.Done() + return + } + ast, err := assistant.Get(assistantID) + if err != nil { + c.JSON(500, gin.H{"message": err.Error(), "code": 500}) + c.Done() + return + } + + sid := c.GetString("__sid") + if sid == "" { + c.JSON(400, gin.H{"message": "sid is required", "code": 400}) + c.Done() + return + } + + payload := assistant.APIPayload{Sid: sid} + if err := c.BindJSON(&payload); err != nil { + c.JSON(400, gin.H{"message": "invalid request body", "code": 400}) + c.Done() + return + } + + result, err := ast.Call(c, payload) + if err != nil { + c.JSON(500, gin.H{"message": err.Error(), "code": 500}) + c.Done() + return + } + + c.JSON(200, result) + c.Done() +} + // handleAssistantDetail handles getting a single assistant's details func (neo *DSL) handleAssistantDetail(c *gin.Context) { assistantID := c.Param("id") diff --git a/neo/assistant/api.go b/neo/assistant/api.go index 142e86ee..06a850e6 100644 --- a/neo/assistant/api.go +++ b/neo/assistant/api.go @@ -170,6 +170,25 @@ func (next *NextAction) Execute(c *gin.Context, ctx chatctx.Context) error { } } +// Call implements the call functionality +func (ast *Assistant) Call(c *gin.Context, payload APIPayload) (interface{}, error) { + scriptCtx, err := ast.Script.NewContext(payload.Sid, nil) + if err != nil { + return nil, err + } + defer scriptCtx.Close() + ctx := c.Request.Context() + + method := fmt.Sprintf("%sAPI", payload.Name) + + // Check if the method exists + if !scriptCtx.Global().Has(method) { + return nil, fmt.Errorf(HookErrorMethodNotFound) + } + + return scriptCtx.CallWith(ctx, method, payload.Payload) +} + // handleChatStream manages the streaming chat interaction with the AI func (ast *Assistant) handleChatStream(c *gin.Context, ctx chatctx.Context, messages []chatMessage.Message, options map[string]interface{}, contents *chatMessage.Contents) error { clientBreak := make(chan bool, 1) diff --git a/neo/assistant/types.go b/neo/assistant/types.go index a6c167c6..52c58ad5 100644 --- a/neo/assistant/types.go +++ b/neo/assistant/types.go @@ -25,6 +25,14 @@ type API interface { Download(ctx context.Context, fileID string) (*FileResponse, error) ReadBase64(ctx context.Context, fileID string) (string, error) Execute(c *gin.Context, ctx chatctx.Context, input string, options map[string]interface{}) error + Call(c *gin.Context, payload APIPayload) (interface{}, error) +} + +// APIPayload the API payload +type APIPayload struct { + Sid string `json:"sid"` + Name string `json:"name"` + Payload map[string]interface{} `json:"payload"` } // ResHookInit the response of the init hook