Remove deprecated studio package and refactor agent integration - Deleted the studio package, which is no longer in use, to streamline the codebase. - Updated references in the agent and chat modules to utilize the new agent package instead of the deprecated neo package. - Ensured that all related middleware and routing functionalities are now aligned with the agent architecture, enhancing overall system coherence.
452 lines
10 KiB
Go
452 lines
10 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/yaoapp/gou/process"
|
|
"github.com/yaoapp/gou/rag/driver"
|
|
"github.com/yaoapp/kun/exception"
|
|
"github.com/yaoapp/yao/agent/message"
|
|
"github.com/yaoapp/yao/agent/store"
|
|
)
|
|
|
|
// GetAgent returns the Agent instance
|
|
func GetAgent() *DSL {
|
|
if Agent == nil {
|
|
exception.New("Agent is not initialized", 500).Throw()
|
|
}
|
|
return Agent
|
|
}
|
|
|
|
func init() {
|
|
process.RegisterGroup("agent", map[string]process.Handler{
|
|
"write": ProcessWrite,
|
|
"assistant.create": processAssistantCreate,
|
|
"assistant.save": processAssistantSave,
|
|
"assistant.delete": processAssistantDelete,
|
|
"assistant.search": processAssistantSearch,
|
|
"assistant.find": processAssistantFind,
|
|
"assistant.match": processAssistantMatch, // Match assistant by content and params
|
|
})
|
|
|
|
// Neo is deprecated, use agent instead (for backward compatibility, It will be removed in the future)
|
|
process.RegisterGroup("neo", map[string]process.Handler{
|
|
"write": ProcessWrite,
|
|
"assistant.create": processAssistantCreate,
|
|
"assistant.save": processAssistantSave,
|
|
"assistant.delete": processAssistantDelete,
|
|
"assistant.search": processAssistantSearch,
|
|
"assistant.find": processAssistantFind,
|
|
"assistant.match": processAssistantMatch, // Match assistant by content and params
|
|
})
|
|
}
|
|
|
|
// ProcessWrite process the write request
|
|
func ProcessWrite(process *process.Process) interface{} {
|
|
process.ValidateArgNums(2)
|
|
|
|
w, ok := process.Args[0].(gin.ResponseWriter)
|
|
if !ok {
|
|
exception.New("The first argument must be a io.Writer", 400).Throw()
|
|
return nil
|
|
}
|
|
|
|
data, ok := process.Args[1].([]interface{})
|
|
if !ok {
|
|
exception.New("The second argument must be a Array", 400).Throw()
|
|
return nil
|
|
}
|
|
|
|
for _, new := range data {
|
|
if v, ok := new.(map[string]interface{}); ok {
|
|
newMsg := message.New().Map(v)
|
|
newMsg.Write(w)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// processAssistantCreate process the assistant create request
|
|
func processAssistantCreate(process *process.Process) interface{} {
|
|
process.ValidateArgNums(1)
|
|
data := process.ArgsMap(0)
|
|
|
|
agent := GetAgent()
|
|
if agent.Store == nil {
|
|
exception.New("Agent store is not initialized", 500).Throw()
|
|
}
|
|
|
|
id, err := agent.Store.SaveAssistant(data)
|
|
if err != nil {
|
|
exception.New("Failed to create assistant: %s", 500, err.Error()).Throw()
|
|
}
|
|
|
|
return id
|
|
}
|
|
|
|
// processAssistantSave process the assistant save request
|
|
func processAssistantSave(process *process.Process) interface{} {
|
|
process.ValidateArgNums(1)
|
|
data := process.ArgsMap(0)
|
|
|
|
agent := GetAgent()
|
|
if agent.Store == nil {
|
|
exception.New("Agent store is not initialized", 500).Throw()
|
|
}
|
|
|
|
id, err := agent.Store.SaveAssistant(data)
|
|
if err != nil {
|
|
exception.New("Failed to save assistant: %s", 500, err.Error()).Throw()
|
|
}
|
|
|
|
return id
|
|
}
|
|
|
|
// processAssistantDelete process the assistant delete request
|
|
func processAssistantDelete(process *process.Process) interface{} {
|
|
process.ValidateArgNums(1)
|
|
assistantID := process.ArgsString(0)
|
|
|
|
agent := GetAgent()
|
|
if agent.Store == nil {
|
|
exception.New("Agent store is not initialized", 500).Throw()
|
|
}
|
|
|
|
err := agent.Store.DeleteAssistant(assistantID)
|
|
if err != nil {
|
|
exception.New("Failed to delete assistant: %s", 500, err.Error()).Throw()
|
|
}
|
|
|
|
return gin.H{"message": "ok"}
|
|
}
|
|
|
|
// processAssistantMatch process the assistant match request
|
|
func processAssistantMatch(process *process.Process) interface{} {
|
|
process.ValidateArgNums(1)
|
|
content := process.Args[0]
|
|
params := map[string]interface{}{}
|
|
if len(process.Args) > 1 {
|
|
params = process.ArgsMap(1)
|
|
}
|
|
|
|
// Limit default to 20
|
|
if _, has := params["limit"]; !has {
|
|
params["limit"] = 20
|
|
}
|
|
|
|
// Max limit to 100
|
|
if limit, has := params["limit"]; has {
|
|
switch v := limit.(type) {
|
|
case int:
|
|
if v > 100 {
|
|
params["limit"] = 100
|
|
}
|
|
case string:
|
|
limitInt, err := strconv.Atoi(v)
|
|
if err != nil {
|
|
exception.New("Invalid limit type: %T", 500, limit).Throw()
|
|
}
|
|
|
|
params["limit"] = limitInt
|
|
if limitInt > 100 {
|
|
params["limit"] = 100
|
|
}
|
|
|
|
default:
|
|
exception.New("Invalid limit type: %T", 500, limit).Throw()
|
|
}
|
|
}
|
|
|
|
// Force Using sotre
|
|
forceStore := false
|
|
if store, has := params["store"]; has {
|
|
switch v := store.(type) {
|
|
case bool:
|
|
forceStore = v
|
|
case int:
|
|
forceStore = v == 1
|
|
case string:
|
|
forceStore = v == "true" || v == "1"
|
|
}
|
|
}
|
|
|
|
// Rag Support match using RAG
|
|
if Agent.RAG != nil && !forceStore {
|
|
return assistantMatchRAG(content, params)
|
|
}
|
|
|
|
// Match using Store
|
|
return assistantMatchStore(content, params)
|
|
}
|
|
|
|
func assistantMatchRAG(content interface{}, params map[string]interface{}) interface{} {
|
|
if Agent == nil {
|
|
exception.New("Agent is not initialized", 500).Throw()
|
|
}
|
|
|
|
// Convert content to JSON string
|
|
var contentStr string
|
|
switch v := content.(type) {
|
|
case string:
|
|
contentStr = v
|
|
case []byte:
|
|
contentStr = string(v)
|
|
default:
|
|
bytes, err := json.Marshal(v)
|
|
if err != nil {
|
|
exception.New("Failed to convert content to JSON: %s", 500, err.Error()).Throw()
|
|
}
|
|
contentStr = string(bytes)
|
|
}
|
|
|
|
// Get limit from params
|
|
limit := 20 // default limit
|
|
if v, has := params["limit"]; has {
|
|
switch lv := v.(type) {
|
|
case int:
|
|
limit = lv
|
|
case string:
|
|
limitInt, err := strconv.Atoi(lv)
|
|
if err == nil {
|
|
limit = limitInt
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get min_score from params
|
|
minScore := 0.0 // default min_score
|
|
if v, has := params["min_score"]; has {
|
|
switch lv := v.(type) {
|
|
case float64:
|
|
minScore = lv
|
|
case float32:
|
|
minScore = float64(lv)
|
|
case int:
|
|
minScore = float64(lv)
|
|
case string:
|
|
if score, err := strconv.ParseFloat(lv, 64); err == nil {
|
|
minScore = score
|
|
}
|
|
}
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
// Get vectors using vectorizer
|
|
vectors, err := Agent.RAG.Vectorizer().Vectorize(ctx, contentStr)
|
|
if err != nil {
|
|
exception.New("Failed to encode content: %s", 500, err.Error()).Throw()
|
|
}
|
|
|
|
// Search using RAG engine
|
|
opts := driver.VectorSearchOptions{
|
|
TopK: limit,
|
|
MinScore: minScore,
|
|
QueryText: contentStr,
|
|
}
|
|
|
|
index := fmt.Sprintf("%sassistants", Agent.RAG.Setting().IndexPrefix)
|
|
results, err := Agent.RAG.Engine().Search(ctx, index, vectors, opts)
|
|
if err != nil {
|
|
exception.New("Failed to search with RAG: %s", 500, err.Error()).Throw()
|
|
}
|
|
|
|
// Convert results to assistant data array
|
|
ids := []string{}
|
|
|
|
// Collect IDs from search results
|
|
for _, result := range results {
|
|
if result.Metadata != nil {
|
|
if id, ok := result.Metadata["assistant_id"].(string); ok {
|
|
ids = append(ids, id)
|
|
}
|
|
}
|
|
}
|
|
|
|
// If no IDs found, return empty array
|
|
if len(ids) == 0 {
|
|
return []map[string]interface{}{}
|
|
}
|
|
|
|
// Fetch complete assistant data from store using AssistantIDs
|
|
filter := store.AssistantFilter{
|
|
AssistantIDs: ids,
|
|
Page: 1,
|
|
PageSize: len(ids),
|
|
}
|
|
res, err := Agent.Store.GetAssistants(filter)
|
|
if err != nil {
|
|
exception.New("get assistants error: %s", 500, err).Throw()
|
|
}
|
|
|
|
return res.Data
|
|
}
|
|
|
|
// parseAssistantFilter parse common filter parameters
|
|
func parseAssistantFilter(params map[string]interface{}) store.AssistantFilter {
|
|
filter := store.AssistantFilter{}
|
|
|
|
// Parse page and pagesize
|
|
if page, ok := params["page"]; ok {
|
|
pageStr := fmt.Sprintf("%v", page)
|
|
if pageInt, err := strconv.Atoi(pageStr); err == nil {
|
|
filter.Page = pageInt
|
|
}
|
|
}
|
|
|
|
if pagesize, ok := params["pagesize"]; ok {
|
|
pagesizeStr := fmt.Sprintf("%v", pagesize)
|
|
if pagesizeInt, err := strconv.Atoi(pagesizeStr); err == nil {
|
|
filter.PageSize = pagesizeInt
|
|
}
|
|
}
|
|
|
|
// select
|
|
if sel, ok := params["select"]; ok {
|
|
switch v := sel.(type) {
|
|
case []interface{}:
|
|
filter.Select = []string{}
|
|
for _, field := range v {
|
|
switch field.(type) {
|
|
case string:
|
|
filter.Select = append(filter.Select, field.(string))
|
|
case interface{}:
|
|
filter.Select = append(filter.Select, fmt.Sprintf("%v", field))
|
|
}
|
|
}
|
|
|
|
case []string:
|
|
filter.Select = v
|
|
|
|
case string:
|
|
fields := strings.Split(v, ",")
|
|
filter.Select = fields
|
|
}
|
|
}
|
|
|
|
// Parse tags
|
|
if tags, ok := params["tags"]; ok {
|
|
switch v := tags.(type) {
|
|
case []interface{}:
|
|
filter.Tags = make([]string, len(v))
|
|
for i, tag := range v {
|
|
filter.Tags[i] = fmt.Sprintf("%v", tag)
|
|
}
|
|
case []string:
|
|
filter.Tags = v
|
|
}
|
|
}
|
|
|
|
// Parse keywords
|
|
if keywords, ok := params["keywords"].(string); ok {
|
|
filter.Keywords = keywords
|
|
}
|
|
|
|
// Parse connector
|
|
if connector, ok := params["connector"].(string); ok {
|
|
filter.Connector = connector
|
|
}
|
|
|
|
// Parse mentionable
|
|
if mentionable, ok := params["mentionable"].(bool); ok {
|
|
filter.Mentionable = &mentionable
|
|
}
|
|
|
|
// Parse automated
|
|
if automated, ok := params["automated"].(bool); ok {
|
|
filter.Automated = &automated
|
|
}
|
|
|
|
return filter
|
|
}
|
|
|
|
func assistantMatchStore(content interface{}, params map[string]interface{}) interface{} {
|
|
agent := GetAgent()
|
|
if agent.Store == nil {
|
|
exception.New("Agent store is not initialized", 500).Throw()
|
|
}
|
|
|
|
// Convert limit to pagesize
|
|
if limit, has := params["limit"]; has {
|
|
params["pagesize"] = limit
|
|
}
|
|
params["page"] = 1
|
|
|
|
// Parse content to keywords if not empty
|
|
if content != nil {
|
|
contentStr := fmt.Sprintf("%v", content)
|
|
if contentStr != "" {
|
|
params["keywords"] = contentStr
|
|
}
|
|
}
|
|
|
|
filter := parseAssistantFilter(params)
|
|
res, err := agent.Store.GetAssistants(filter)
|
|
if err != nil {
|
|
exception.New("get assistants error: %s", 500, err).Throw()
|
|
}
|
|
|
|
return res.Data
|
|
}
|
|
|
|
// processAssistantSearch process the assistant search request
|
|
func processAssistantSearch(process *process.Process) interface{} {
|
|
params := process.ArgsMap(0)
|
|
filter := parseAssistantFilter(params)
|
|
|
|
// Get assistants
|
|
agent := GetAgent()
|
|
if agent.Store == nil {
|
|
exception.New("Agent store is not initialized", 500).Throw()
|
|
}
|
|
|
|
locale := "en"
|
|
if len(process.Args) > 1 {
|
|
locale = process.ArgsString(1)
|
|
}
|
|
|
|
res, err := agent.Store.GetAssistants(filter, locale)
|
|
if err != nil {
|
|
exception.New("get assistants error: %s", 500, err).Throw()
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
// processAssistantFind process the assistant find request
|
|
func processAssistantFind(process *process.Process) interface{} {
|
|
process.ValidateArgNums(1)
|
|
assistantID := process.ArgsString(0)
|
|
|
|
agent := GetAgent()
|
|
if agent.Store == nil {
|
|
exception.New("Agent store is not initialized", 500).Throw()
|
|
}
|
|
|
|
filter := store.AssistantFilter{
|
|
AssistantID: assistantID,
|
|
Page: 1,
|
|
PageSize: 1,
|
|
}
|
|
|
|
locale := "en"
|
|
if len(process.Args) > 1 {
|
|
locale = process.ArgsString(1)
|
|
}
|
|
res, err := agent.Store.GetAssistants(filter, locale)
|
|
if err != nil {
|
|
exception.New("Failed to find assistant: %s", 500, err.Error()).Throw()
|
|
}
|
|
|
|
if len(res.Data) == 0 {
|
|
exception.New("Assistant not found: %s", 404, assistantID).Throw()
|
|
}
|
|
|
|
return res.Data[0]
|
|
}
|