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