yao/neo/process.go
Max 4ab7a1056e Enhance localization support and refactor assistant functions for improved i18n handling
- Introduced locale parameter in GetAssistants and GetAssistant methods across various store implementations to support localized responses.
- Updated handleChatLatest, handleAssistantList, handleAssistantDetail, and handleAssistantTags functions to utilize the new locale handling for better user experience.
- Refactored GetPlaceholder method in the Assistant struct to accept a locale argument for dynamic placeholder translations.
- Removed redundant i18n code and centralized translation logic for cleaner implementation.
2025-05-26 11:58:48 +08:00

441 lines
10 KiB
Go

package neo
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/neo/message"
"github.com/yaoapp/yao/neo/store"
)
// GetNeo returns the Neo instance
func GetNeo() *DSL {
if Neo == nil {
exception.New("Neo is not initialized", 500).Throw()
}
return Neo
}
func init() {
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)
neo := GetNeo()
if neo.Store == nil {
exception.New("Neo store is not initialized", 500).Throw()
}
id, err := neo.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)
neo := GetNeo()
if neo.Store == nil {
exception.New("Neo store is not initialized", 500).Throw()
}
id, err := neo.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)
neo := GetNeo()
if neo.Store == nil {
exception.New("Neo store is not initialized", 500).Throw()
}
err := neo.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 Neo.RAG != nil && !forceStore {
return assistantMatchRAG(content, params)
}
// Match using Store
return assistantMatchStore(content, params)
}
func assistantMatchRAG(content interface{}, params map[string]interface{}) interface{} {
if Neo == nil {
exception.New("Neo 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 := Neo.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", Neo.RAG.Setting().IndexPrefix)
results, err := Neo.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 := Neo.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{} {
neo := GetNeo()
if neo.Store == nil {
exception.New("Neo 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 := neo.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
neo := GetNeo()
if neo.Store == nil {
exception.New("Neo store is not initialized", 500).Throw()
}
locale := "en"
if len(process.Args) > 1 {
locale = process.ArgsString(1)
}
res, err := neo.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)
neo := GetNeo()
if neo.Store == nil {
exception.New("Neo 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 := neo.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]
}