Enhance context handling and metadata management in assistant operations
- Introduced a new test context creation function to streamline test setup for assistant operations. - Refactored context structure to replace the deprecated 'Data' field with 'Metadata', improving clarity and consistency. - Updated various methods to utilize the new 'Metadata' field, ensuring proper handling of request metadata across the system. - Adjusted tests to reflect changes in metadata handling, enhancing validation of context fields in JavaScript integration.
This commit is contained in:
parent
e0f69368fb
commit
aa69b693e3
7 changed files with 420 additions and 108 deletions
|
|
@ -1,14 +1,60 @@
|
|||
package hook_test
|
||||
|
||||
import (
|
||||
defaultContext "context"
|
||||
stdContext "context"
|
||||
"testing"
|
||||
|
||||
"github.com/yaoapp/gou/plan"
|
||||
"github.com/yaoapp/yao/agent/assistant"
|
||||
"github.com/yaoapp/yao/agent/context"
|
||||
"github.com/yaoapp/yao/agent/testutils"
|
||||
"github.com/yaoapp/yao/openapi/oauth/types"
|
||||
)
|
||||
|
||||
// newTestContext creates a Context for testing with commonly used fields pre-populated.
|
||||
// You can override any fields after creation as needed for specific test scenarios.
|
||||
func newTestContext(chatID, assistantID string) *context.Context {
|
||||
return &context.Context{
|
||||
Context: stdContext.Background(),
|
||||
Space: plan.NewMemorySharedSpace(),
|
||||
ChatID: chatID,
|
||||
AssistantID: assistantID,
|
||||
Connector: "",
|
||||
Locale: "en-us",
|
||||
Theme: "light",
|
||||
Client: context.Client{
|
||||
Type: "web",
|
||||
UserAgent: "TestAgent/1.0",
|
||||
IP: "127.0.0.1",
|
||||
},
|
||||
Referer: context.RefererAPI,
|
||||
Accept: context.AcceptWebCUI,
|
||||
Route: "",
|
||||
Metadata: make(map[string]interface{}),
|
||||
Authorized: &types.AuthorizedInfo{
|
||||
Subject: "test-user",
|
||||
ClientID: "test-client-id",
|
||||
Scope: "openid profile email",
|
||||
SessionID: "test-session-id",
|
||||
UserID: "test-user-123",
|
||||
TeamID: "test-team-456",
|
||||
TenantID: "test-tenant-789",
|
||||
RememberMe: true,
|
||||
Constraints: types.DataConstraints{
|
||||
OwnerOnly: false,
|
||||
CreatorOnly: false,
|
||||
EditorOnly: false,
|
||||
TeamOnly: true,
|
||||
Extra: map[string]interface{}{
|
||||
"department": "engineering",
|
||||
"region": "us-west",
|
||||
"project": "yao",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreate test the create hook
|
||||
func TestCreate(t *testing.T) {
|
||||
testutils.Prepare(t)
|
||||
|
|
@ -23,12 +69,8 @@ func TestCreate(t *testing.T) {
|
|||
t.Fatalf("The tests.create assistant has no script")
|
||||
}
|
||||
|
||||
ctx := &context.Context{
|
||||
Context: defaultContext.Background(),
|
||||
ChatID: "chat-test-create-hook",
|
||||
AssistantID: "tests.create",
|
||||
Sid: "test-session-create-hook",
|
||||
}
|
||||
// Use the helper function to create a test context
|
||||
ctx := newTestContext("chat-test-create-hook", "tests.create")
|
||||
|
||||
// Test scenario 1: Return null (should get nil response)
|
||||
t.Run("ReturnNull", func(t *testing.T) {
|
||||
|
|
@ -120,18 +162,6 @@ func TestCreate(t *testing.T) {
|
|||
} else if *res.MaxCompletionTokens != 1500 {
|
||||
t.Errorf("Expected max_completion_tokens 1500, got: %d", *res.MaxCompletionTokens)
|
||||
}
|
||||
|
||||
// Verify metadata
|
||||
if res.Metadata == nil {
|
||||
t.Error("Expected metadata, got nil")
|
||||
} else {
|
||||
if res.Metadata["test"] != "full_response" {
|
||||
t.Errorf("Expected metadata['test'] = 'full_response', got: %s", res.Metadata["test"])
|
||||
}
|
||||
if res.Metadata["user_id"] != "test_user_123" {
|
||||
t.Errorf("Expected metadata['user_id'] = 'test_user_123', got: %s", res.Metadata["user_id"])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Test scenario 5: Return partial response
|
||||
|
|
@ -190,19 +220,6 @@ func TestCreate(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify metadata
|
||||
if res.Metadata == nil {
|
||||
t.Error("Expected metadata, got nil")
|
||||
} else {
|
||||
if res.Metadata["test"] != "process_call" {
|
||||
t.Errorf("Expected metadata['test'] = 'process_call', got: %s", res.Metadata["test"])
|
||||
}
|
||||
// roles_count should be present
|
||||
if _, ok := res.Metadata["roles_count"]; !ok {
|
||||
t.Error("Expected metadata['roles_count'] to be present")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Test scenario 7: Default response
|
||||
|
|
|
|||
|
|
@ -92,12 +92,21 @@ func (ctx *Context) Map() map[string]interface{} {
|
|||
data := map[string]interface{}{}
|
||||
|
||||
// Authorized information
|
||||
if ctx.Authorized != nil {
|
||||
data["authorized"] = ctx.Authorized
|
||||
}
|
||||
if ctx.ChatID != "" {
|
||||
data["chat_id"] = ctx.ChatID
|
||||
}
|
||||
if ctx.AssistantID != "" {
|
||||
data["assistant_id"] = ctx.AssistantID
|
||||
}
|
||||
if ctx.Connector != "" {
|
||||
data["connector"] = ctx.Connector
|
||||
}
|
||||
if ctx.Search != nil {
|
||||
data["search"] = *ctx.Search
|
||||
}
|
||||
|
||||
// Arguments for call
|
||||
if len(ctx.Args) > 0 {
|
||||
|
|
@ -137,8 +146,8 @@ func (ctx *Context) Map() map[string]interface{} {
|
|||
if ctx.Route != "" {
|
||||
data["route"] = ctx.Route
|
||||
}
|
||||
if len(ctx.Data) > 0 {
|
||||
data["data"] = ctx.Data
|
||||
if len(ctx.Metadata) > 0 {
|
||||
data["metadata"] = ctx.Metadata
|
||||
}
|
||||
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/yaoapp/gou/runtime/v8/bridge"
|
||||
"rogchap.com/v8go"
|
||||
)
|
||||
|
||||
|
|
@ -25,15 +26,75 @@ func (ctx *Context) NewObject(v8ctx *v8go.Context) (*v8go.Value, error) {
|
|||
jsObject.Set("__id", id)
|
||||
jsObject.Set("__release", ctx.objectRelease(v8ctx.Isolate(), id))
|
||||
|
||||
jsObject.Set("ChatID", ctx.ChatID)
|
||||
jsObject.Set("AssistantID", ctx.AssistantID)
|
||||
jsObject.Set("Sid", ctx.Sid)
|
||||
// Set primitive fields in template
|
||||
jsObject.Set("chat_id", ctx.ChatID)
|
||||
jsObject.Set("assistant_id", ctx.AssistantID)
|
||||
jsObject.Set("connector", ctx.Connector)
|
||||
if ctx.Search != nil {
|
||||
jsObject.Set("search", *ctx.Search)
|
||||
}
|
||||
|
||||
jsObject.Set("retry", ctx.Retry)
|
||||
jsObject.Set("retry_times", uint32(ctx.RetryTimes))
|
||||
jsObject.Set("locale", ctx.Locale)
|
||||
jsObject.Set("theme", ctx.Theme)
|
||||
jsObject.Set("referer", ctx.Referer)
|
||||
jsObject.Set("accept", string(ctx.Accept))
|
||||
jsObject.Set("route", ctx.Route)
|
||||
|
||||
// Create instance
|
||||
instance, err := jsObject.NewInstance(v8ctx)
|
||||
if err != nil {
|
||||
ctx.objectRelease(v8ctx.Isolate(), id)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
obj, err := instance.Value.AsObject()
|
||||
if err != nil {
|
||||
ctx.objectRelease(v8ctx.Isolate(), id)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set complex objects (maps, arrays) after instance creation using bridge
|
||||
// Args array
|
||||
if ctx.Args != nil {
|
||||
argsVal, err := bridge.JsValue(v8ctx, ctx.Args)
|
||||
if err == nil {
|
||||
obj.Set("args", argsVal)
|
||||
argsVal.Release() // Release Go-side Persistent handle, V8 internal reference remains
|
||||
}
|
||||
}
|
||||
|
||||
// Client object
|
||||
clientData := map[string]interface{}{
|
||||
"type": ctx.Client.Type,
|
||||
"user_agent": ctx.Client.UserAgent,
|
||||
"ip": ctx.Client.IP,
|
||||
}
|
||||
clientVal, err := bridge.JsValue(v8ctx, clientData)
|
||||
if err == nil {
|
||||
obj.Set("client", clientVal)
|
||||
clientVal.Release() // Release Go-side Persistent handle, V8 internal reference remains
|
||||
}
|
||||
|
||||
// Metadata object
|
||||
if ctx.Metadata != nil {
|
||||
metadataVal, err := bridge.JsValue(v8ctx, ctx.Metadata)
|
||||
if err == nil {
|
||||
obj.Set("metadata", metadataVal)
|
||||
metadataVal.Release() // Release Go-side Persistent handle, V8 internal reference remains
|
||||
}
|
||||
}
|
||||
|
||||
// Authorized object
|
||||
if ctx.Authorized != nil {
|
||||
authorizedVal, err := bridge.JsValue(v8ctx, ctx.Authorized)
|
||||
if err == nil {
|
||||
obj.Set("authorized", authorizedVal)
|
||||
authorizedVal.Release() // Release Go-side Persistent handle, V8 internal reference remains
|
||||
}
|
||||
}
|
||||
|
||||
return instance.Value, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
v8 "github.com/yaoapp/gou/runtime/v8"
|
||||
"github.com/yaoapp/gou/runtime/v8/bridge"
|
||||
"github.com/yaoapp/yao/config"
|
||||
"github.com/yaoapp/yao/openapi/oauth/types"
|
||||
"github.com/yaoapp/yao/test"
|
||||
"rogchap.com/v8go"
|
||||
)
|
||||
|
|
@ -52,7 +53,7 @@ func testContextJsvalueFunction(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
|||
return bridge.JsException(info.Context(), err)
|
||||
}
|
||||
|
||||
chatID, err := ctx.Get("ChatID")
|
||||
chatID, err := ctx.Get("chat_id")
|
||||
if err != nil {
|
||||
return bridge.JsException(info.Context(), err)
|
||||
}
|
||||
|
|
@ -221,3 +222,223 @@ func testContextRegistrationFunction(info *v8go.FunctionCallbackInfo) *v8go.Valu
|
|||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// TestJsValueAllFields test that all Context fields are properly exported to JavaScript
|
||||
func TestJsValueAllFields(t *testing.T) {
|
||||
|
||||
test.Prepare(t, config.Conf)
|
||||
defer test.Clean()
|
||||
|
||||
searchTrue := true
|
||||
cxt := &Context{
|
||||
ChatID: "test-chat-id",
|
||||
AssistantID: "test-assistant-id",
|
||||
Connector: "test-connector",
|
||||
Search: &searchTrue,
|
||||
Args: []interface{}{"arg1", "arg2", 123},
|
||||
Retry: true,
|
||||
RetryTimes: 3,
|
||||
Locale: "zh-cn",
|
||||
Theme: "dark",
|
||||
Client: Client{
|
||||
Type: "web",
|
||||
UserAgent: "Mozilla/5.0",
|
||||
IP: "127.0.0.1",
|
||||
},
|
||||
Referer: "api",
|
||||
Accept: "cui-web",
|
||||
Route: "/dashboard/home",
|
||||
Metadata: map[string]interface{}{
|
||||
"key1": "value1",
|
||||
"key2": 123,
|
||||
"key3": true,
|
||||
},
|
||||
Authorized: &types.AuthorizedInfo{
|
||||
Subject: "test-user",
|
||||
ClientID: "test-client",
|
||||
UserID: "user-123",
|
||||
TeamID: "team-456",
|
||||
TenantID: "tenant-789",
|
||||
Constraints: types.DataConstraints{
|
||||
OwnerOnly: true,
|
||||
CreatorOnly: false,
|
||||
TeamOnly: true,
|
||||
Extra: map[string]interface{}{
|
||||
"department": "engineering",
|
||||
"region": "us-west",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
v8.RegisterFunction("testAllFields", testAllFieldsEmbed)
|
||||
res, err := v8.Call(v8.CallOptions{}, `
|
||||
function test(cxt) {
|
||||
return testAllFields(cxt)
|
||||
}`, cxt)
|
||||
if err != nil {
|
||||
t.Fatalf("Call failed: %v", err)
|
||||
}
|
||||
|
||||
result, ok := res.(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("Expected map result, got %T", res)
|
||||
}
|
||||
|
||||
// Verify all fields
|
||||
assert.Equal(t, "test-chat-id", result["chat_id"], "chat_id mismatch")
|
||||
assert.Equal(t, "test-assistant-id", result["assistant_id"], "assistant_id mismatch")
|
||||
assert.Equal(t, "test-connector", result["connector"], "connector mismatch")
|
||||
assert.Equal(t, true, result["search"], "search mismatch")
|
||||
assert.Equal(t, true, result["retry"], "retry mismatch")
|
||||
assert.Equal(t, float64(3), result["retry_times"], "retry_times mismatch")
|
||||
assert.Equal(t, "zh-cn", result["locale"], "locale mismatch")
|
||||
assert.Equal(t, "dark", result["theme"], "theme mismatch")
|
||||
assert.Equal(t, "api", result["referer"], "referer mismatch")
|
||||
assert.Equal(t, "cui-web", result["accept"], "accept mismatch")
|
||||
assert.Equal(t, "/dashboard/home", result["route"], "route mismatch")
|
||||
|
||||
// Verify args array
|
||||
args, ok := result["args"].([]interface{})
|
||||
assert.True(t, ok, "args should be an array")
|
||||
assert.Equal(t, 3, len(args), "args length mismatch")
|
||||
|
||||
// Verify client object
|
||||
client, ok := result["client"].(map[string]interface{})
|
||||
assert.True(t, ok, "client should be an object")
|
||||
assert.Equal(t, "web", client["type"], "client.type mismatch")
|
||||
assert.Equal(t, "Mozilla/5.0", client["user_agent"], "client.user_agent mismatch")
|
||||
assert.Equal(t, "127.0.0.1", client["ip"], "client.ip mismatch")
|
||||
|
||||
// Verify metadata object
|
||||
metadata, ok := result["metadata"].(map[string]interface{})
|
||||
assert.True(t, ok, "metadata should be an object")
|
||||
assert.Equal(t, "value1", metadata["key1"], "metadata.key1 mismatch")
|
||||
assert.Equal(t, float64(123), metadata["key2"], "metadata.key2 mismatch")
|
||||
assert.Equal(t, true, metadata["key3"], "metadata.key3 mismatch")
|
||||
|
||||
// Verify authorized object
|
||||
authorized, ok := result["authorized"].(map[string]interface{})
|
||||
assert.True(t, ok, "authorized should be an object")
|
||||
assert.Equal(t, "test-user", authorized["sub"], "authorized.sub mismatch")
|
||||
assert.Equal(t, "test-client", authorized["client_id"], "authorized.client_id mismatch")
|
||||
assert.Equal(t, "user-123", authorized["user_id"], "authorized.user_id mismatch")
|
||||
assert.Equal(t, "team-456", authorized["team_id"], "authorized.team_id mismatch")
|
||||
assert.Equal(t, "tenant-789", authorized["tenant_id"], "authorized.tenant_id mismatch")
|
||||
|
||||
// Verify authorized.constraints object
|
||||
constraints, ok := authorized["constraints"].(map[string]interface{})
|
||||
assert.True(t, ok, "authorized.constraints should be an object")
|
||||
assert.Equal(t, true, constraints["owner_only"], "constraints.owner_only mismatch")
|
||||
// creator_only is false, and with omitempty it may not be present
|
||||
if creatorOnly, exists := constraints["creator_only"]; exists {
|
||||
assert.Equal(t, false, creatorOnly, "constraints.creator_only mismatch")
|
||||
}
|
||||
assert.Equal(t, true, constraints["team_only"], "constraints.team_only mismatch")
|
||||
|
||||
// Verify constraints.extra object
|
||||
extra, ok := constraints["extra"].(map[string]interface{})
|
||||
assert.True(t, ok, "constraints.extra should be an object")
|
||||
assert.Equal(t, "engineering", extra["department"], "constraints.extra.department mismatch")
|
||||
assert.Equal(t, "us-west", extra["region"], "constraints.extra.region mismatch")
|
||||
|
||||
// Verify deprecated fields are NOT exported
|
||||
_, hasSid := result["sid"]
|
||||
assert.False(t, hasSid, "sid (deprecated) should not be exported")
|
||||
_, hasSilent := result["silent"]
|
||||
assert.False(t, hasSilent, "silent (deprecated) should not be exported")
|
||||
|
||||
assert.Equal(t, 0, len(objects))
|
||||
}
|
||||
|
||||
func testAllFieldsEmbed(iso *v8go.Isolate) *v8go.FunctionTemplate {
|
||||
return v8go.NewFunctionTemplate(iso, testAllFieldsFunction)
|
||||
}
|
||||
|
||||
func testAllFieldsFunction(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
||||
var args = info.Args()
|
||||
if len(args) < 1 {
|
||||
return bridge.JsException(info.Context(), "Missing parameters")
|
||||
}
|
||||
|
||||
ctx, err := args[0].AsObject()
|
||||
if err != nil {
|
||||
return bridge.JsException(info.Context(), err)
|
||||
}
|
||||
|
||||
// Extract all fields and return as a map
|
||||
result := map[string]interface{}{}
|
||||
|
||||
// Helper function to get field value
|
||||
getField := func(name string) (interface{}, bool) {
|
||||
val, err := ctx.Get(name)
|
||||
if err != nil || val.IsUndefined() {
|
||||
return nil, false
|
||||
}
|
||||
goVal, err := bridge.GoValue(val, info.Context())
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return goVal, true
|
||||
}
|
||||
|
||||
if val, ok := getField("chat_id"); ok {
|
||||
result["chat_id"] = val
|
||||
}
|
||||
if val, ok := getField("assistant_id"); ok {
|
||||
result["assistant_id"] = val
|
||||
}
|
||||
if val, ok := getField("connector"); ok {
|
||||
result["connector"] = val
|
||||
}
|
||||
if val, ok := getField("search"); ok {
|
||||
result["search"] = val
|
||||
}
|
||||
if val, ok := getField("args"); ok {
|
||||
result["args"] = val
|
||||
}
|
||||
if val, ok := getField("retry"); ok {
|
||||
result["retry"] = val
|
||||
}
|
||||
if val, ok := getField("retry_times"); ok {
|
||||
result["retry_times"] = val
|
||||
}
|
||||
if val, ok := getField("locale"); ok {
|
||||
result["locale"] = val
|
||||
}
|
||||
if val, ok := getField("theme"); ok {
|
||||
result["theme"] = val
|
||||
}
|
||||
if val, ok := getField("client"); ok {
|
||||
result["client"] = val
|
||||
}
|
||||
if val, ok := getField("referer"); ok {
|
||||
result["referer"] = val
|
||||
}
|
||||
if val, ok := getField("accept"); ok {
|
||||
result["accept"] = val
|
||||
}
|
||||
if val, ok := getField("route"); ok {
|
||||
result["route"] = val
|
||||
}
|
||||
if val, ok := getField("metadata"); ok {
|
||||
result["metadata"] = val
|
||||
}
|
||||
if val, ok := getField("authorized"); ok {
|
||||
result["authorized"] = val
|
||||
}
|
||||
|
||||
// Check for deprecated fields - they should NOT exist
|
||||
if val, ok := getField("sid"); ok {
|
||||
result["sid"] = val
|
||||
}
|
||||
if val, ok := getField("silent"); ok {
|
||||
result["silent"] = val
|
||||
}
|
||||
|
||||
jsVal, err := bridge.JsValue(info.Context(), result)
|
||||
if err != nil {
|
||||
return bridge.JsException(info.Context(), err)
|
||||
}
|
||||
return jsVal
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,8 +62,8 @@ func GetCompletionRequest(c *gin.Context, cache store.Store) (*CompletionRequest
|
|||
UserAgent: userAgent,
|
||||
IP: clientIP,
|
||||
},
|
||||
Route: GetRoute(c, completionReq),
|
||||
Data: GetData(c, completionReq),
|
||||
Route: GetRoute(c, completionReq),
|
||||
Metadata: GetMetadata(c, completionReq),
|
||||
}
|
||||
|
||||
return completionReq, ctx, nil
|
||||
|
|
@ -184,8 +184,10 @@ func GetLocale(c *gin.Context, req *CompletionRequest) string {
|
|||
|
||||
// Priority 3: From CompletionRequest metadata
|
||||
if req != nil && req.Metadata != nil {
|
||||
if locale, ok := req.Metadata["locale"]; ok && locale != "" {
|
||||
return strings.ToLower(locale)
|
||||
if locale, ok := req.Metadata["locale"]; ok {
|
||||
if localeStr, ok := locale.(string); ok && localeStr != "" {
|
||||
return strings.ToLower(localeStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -209,8 +211,10 @@ func GetTheme(c *gin.Context, req *CompletionRequest) string {
|
|||
|
||||
// Priority 3: From CompletionRequest metadata
|
||||
if req != nil && req.Metadata != nil {
|
||||
if theme, ok := req.Metadata["theme"]; ok && theme != "" {
|
||||
return strings.ToLower(theme)
|
||||
if theme, ok := req.Metadata["theme"]; ok {
|
||||
if themeStr, ok := theme.(string); ok && themeStr != "" {
|
||||
return strings.ToLower(themeStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -235,8 +239,10 @@ func GetReferer(c *gin.Context, req *CompletionRequest) string {
|
|||
|
||||
// Priority 3: From CompletionRequest metadata
|
||||
if req != nil && req.Metadata != nil {
|
||||
if referer, ok := req.Metadata["referer"]; ok && referer != "" {
|
||||
return validateReferer(referer)
|
||||
if referer, ok := req.Metadata["referer"]; ok {
|
||||
if refererStr, ok := referer.(string); ok && refererStr != "" {
|
||||
return validateReferer(refererStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -262,8 +268,10 @@ func GetAccept(c *gin.Context, req *CompletionRequest) Accept {
|
|||
|
||||
// Priority 3: From CompletionRequest metadata
|
||||
if req != nil && req.Metadata != nil {
|
||||
if accept, ok := req.Metadata["accept"]; ok && accept != "" {
|
||||
return validateAccept(accept)
|
||||
if accept, ok := req.Metadata["accept"]; ok {
|
||||
if acceptStr, ok := accept.(string); ok && acceptStr != "" {
|
||||
return validateAccept(acceptStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -292,8 +300,10 @@ func GetChatID(c *gin.Context, cache store.Store, req *CompletionRequest) (strin
|
|||
|
||||
// Priority 3: From CompletionRequest metadata
|
||||
if req != nil && req.Metadata != nil {
|
||||
if chatID, ok := req.Metadata["chat_id"]; ok && chatID != "" {
|
||||
return chatID, nil
|
||||
if chatID, ok := req.Metadata["chat_id"]; ok {
|
||||
if chatIDStr, ok := chatID.(string); ok && chatIDStr != "" {
|
||||
return chatIDStr, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -312,12 +322,12 @@ func GetChatID(c *gin.Context, cache store.Store, req *CompletionRequest) (strin
|
|||
}
|
||||
|
||||
// GetRoute extracts route from request with priority:
|
||||
// 1. Query parameter "yao_route"
|
||||
// 1. Query parameter "route"
|
||||
// 2. Header "X-Yao-Route"
|
||||
// 3. CompletionRequest.Route (from payload)
|
||||
func GetRoute(c *gin.Context, req *CompletionRequest) string {
|
||||
// Priority 1: Query parameter
|
||||
if route := c.Query("yao_route"); route != "" {
|
||||
if route := c.Query("route"); route != "" {
|
||||
return route
|
||||
}
|
||||
|
||||
|
|
@ -334,38 +344,38 @@ func GetRoute(c *gin.Context, req *CompletionRequest) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
// GetData extracts data from request with priority:
|
||||
// 1. Query parameter "yao_data" (JSON string)
|
||||
// 2. Header "X-Yao-Data" (Base64 encoded JSON string)
|
||||
// 3. CompletionRequest.Data (from payload)
|
||||
func GetData(c *gin.Context, req *CompletionRequest) map[string]interface{} {
|
||||
// GetMetadata extracts metadata from request with priority:
|
||||
// 1. Query parameter "metadata" (JSON string)
|
||||
// 2. Header "X-Yao-Metadata" (Base64 encoded JSON string)
|
||||
// 3. CompletionRequest.Metadata (from payload)
|
||||
func GetMetadata(c *gin.Context, req *CompletionRequest) map[string]interface{} {
|
||||
// Priority 1: Query parameter (JSON string)
|
||||
if dataJSON := c.Query("yao_data"); dataJSON != "" {
|
||||
var data map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(dataJSON), &data); err == nil {
|
||||
return data
|
||||
if metadataJSON := c.Query("metadata"); metadataJSON != "" {
|
||||
var metadata map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(metadataJSON), &metadata); err == nil {
|
||||
return metadata
|
||||
}
|
||||
}
|
||||
|
||||
// Priority 2: Header (Base64 encoded JSON string)
|
||||
if dataBase64 := c.GetHeader("X-Yao-Data"); dataBase64 != "" {
|
||||
if metadataBase64 := c.GetHeader("X-Yao-Metadata"); metadataBase64 != "" {
|
||||
// Try to decode Base64
|
||||
if decoded, err := base64.StdEncoding.DecodeString(dataBase64); err == nil {
|
||||
var data map[string]interface{}
|
||||
if err := json.Unmarshal(decoded, &data); err == nil {
|
||||
return data
|
||||
if decoded, err := base64.StdEncoding.DecodeString(metadataBase64); err == nil {
|
||||
var metadata map[string]interface{}
|
||||
if err := json.Unmarshal(decoded, &metadata); err == nil {
|
||||
return metadata
|
||||
}
|
||||
}
|
||||
// Fallback: try to parse as plain JSON (for backward compatibility)
|
||||
var data map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(dataBase64), &data); err == nil {
|
||||
return data
|
||||
// Fallback: try to parse as plain JSON
|
||||
var metadata map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(metadataBase64), &metadata); err == nil {
|
||||
return metadata
|
||||
}
|
||||
}
|
||||
|
||||
// Priority 3: From CompletionRequest
|
||||
if req != nil && req.Data != nil {
|
||||
return req.Data
|
||||
if req != nil && req.Metadata != nil {
|
||||
return req.Metadata
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -469,7 +479,7 @@ func parseCompletionRequestData(c *gin.Context) (*CompletionRequest, error) {
|
|||
|
||||
// Metadata from query (JSON string)
|
||||
if metadataJSON := c.Query("metadata"); metadataJSON != "" {
|
||||
var metadata map[string]string
|
||||
var metadata map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(metadataJSON), &metadata); err == nil {
|
||||
req.Metadata = metadata
|
||||
}
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ func TestGetChatID_FromMetadata(t *testing.T) {
|
|||
"messages": []map[string]interface{}{
|
||||
{"role": "user", "content": "Test"},
|
||||
},
|
||||
"metadata": map[string]string{
|
||||
"metadata": map[string]interface{}{
|
||||
"chat_id": expectedChatID,
|
||||
},
|
||||
}
|
||||
|
|
@ -327,7 +327,7 @@ func TestGetChatID_Priority(t *testing.T) {
|
|||
requestBody := map[string]interface{}{
|
||||
"model": "gpt-4",
|
||||
"messages": messages,
|
||||
"metadata": map[string]string{
|
||||
"metadata": map[string]interface{}{
|
||||
"chat_id": metadataChatID,
|
||||
},
|
||||
}
|
||||
|
|
@ -392,7 +392,7 @@ func TestGetLocale_FromMetadata(t *testing.T) {
|
|||
c.Request = req
|
||||
|
||||
completionReq := &CompletionRequest{
|
||||
Metadata: map[string]string{
|
||||
Metadata: map[string]interface{}{
|
||||
"locale": "ja-JP",
|
||||
},
|
||||
}
|
||||
|
|
@ -413,7 +413,7 @@ func TestGetLocale_Priority(t *testing.T) {
|
|||
c.Request = req
|
||||
|
||||
completionReq := &CompletionRequest{
|
||||
Metadata: map[string]string{
|
||||
Metadata: map[string]interface{}{
|
||||
"locale": "de-DE",
|
||||
},
|
||||
}
|
||||
|
|
@ -462,7 +462,7 @@ func TestGetTheme_FromMetadata(t *testing.T) {
|
|||
c.Request = req
|
||||
|
||||
completionReq := &CompletionRequest{
|
||||
Metadata: map[string]string{
|
||||
Metadata: map[string]interface{}{
|
||||
"theme": "auto",
|
||||
},
|
||||
}
|
||||
|
|
@ -482,7 +482,7 @@ func TestGetReferer_FromMetadata(t *testing.T) {
|
|||
c.Request = req
|
||||
|
||||
completionReq := &CompletionRequest{
|
||||
Metadata: map[string]string{
|
||||
Metadata: map[string]interface{}{
|
||||
"referer": "tool",
|
||||
},
|
||||
}
|
||||
|
|
@ -502,7 +502,7 @@ func TestGetAccept_FromMetadata(t *testing.T) {
|
|||
c.Request = req
|
||||
|
||||
completionReq := &CompletionRequest{
|
||||
Metadata: map[string]string{
|
||||
Metadata: map[string]interface{}{
|
||||
"accept": "cui-native",
|
||||
},
|
||||
}
|
||||
|
|
@ -561,7 +561,7 @@ func TestGetAssistantID_Priority(t *testing.T) {
|
|||
func TestGetRoute_FromQuery(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
req := httptest.NewRequest("GET", "/chat/completions?yao_route=/dashboard/home", nil)
|
||||
req := httptest.NewRequest("GET", "/chat/completions?route=/dashboard/home", nil)
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = req
|
||||
|
|
@ -608,7 +608,7 @@ func TestGetRoute_FromPayload(t *testing.T) {
|
|||
func TestGetRoute_Priority(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
req := httptest.NewRequest("GET", "/chat/completions?yao_route=/from/query", nil)
|
||||
req := httptest.NewRequest("GET", "/chat/completions?route=/from/query", nil)
|
||||
req.Header.Set("X-Yao-Route", "/from/header")
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
|
|
@ -624,7 +624,7 @@ func TestGetRoute_Priority(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetData_FromQuery(t *testing.T) {
|
||||
func TestGetMetadata_FromQuery(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
data := map[string]interface{}{
|
||||
|
|
@ -633,12 +633,12 @@ func TestGetData_FromQuery(t *testing.T) {
|
|||
}
|
||||
dataJSON, _ := json.Marshal(data)
|
||||
|
||||
req := httptest.NewRequest("GET", "/chat/completions?yao_data="+string(dataJSON), nil)
|
||||
req := httptest.NewRequest("GET", "/chat/completions?metadata="+string(dataJSON), nil)
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = req
|
||||
|
||||
result := GetData(c, nil)
|
||||
result := GetMetadata(c, nil)
|
||||
if result == nil {
|
||||
t.Fatal("Expected data to be returned")
|
||||
}
|
||||
|
|
@ -652,18 +652,18 @@ func TestGetData_FromQuery(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetData_FromHeader_Base64(t *testing.T) {
|
||||
func TestGetMetadata_FromHeader_Base64(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
dataBase64 := "eyJ1c2VyX2lkIjo0NTYsImFjdGlvbiI6ImNyZWF0ZSJ9" // base64 of {"user_id":456,"action":"create"}
|
||||
|
||||
req := httptest.NewRequest("GET", "/chat/completions", nil)
|
||||
req.Header.Set("X-Yao-Data", dataBase64)
|
||||
req.Header.Set("X-Yao-Metadata", dataBase64)
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = req
|
||||
|
||||
result := GetData(c, nil)
|
||||
result := GetMetadata(c, nil)
|
||||
if result == nil {
|
||||
t.Fatal("Expected data to be returned")
|
||||
}
|
||||
|
|
@ -677,7 +677,7 @@ func TestGetData_FromHeader_Base64(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetData_FromPayload(t *testing.T) {
|
||||
func TestGetMetadata_FromPayload(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
req := httptest.NewRequest("POST", "/chat/completions", nil)
|
||||
|
|
@ -691,10 +691,10 @@ func TestGetData_FromPayload(t *testing.T) {
|
|||
}
|
||||
|
||||
completionReq := &CompletionRequest{
|
||||
Data: data,
|
||||
Metadata: data,
|
||||
}
|
||||
|
||||
result := GetData(c, completionReq)
|
||||
result := GetMetadata(c, completionReq)
|
||||
if result == nil {
|
||||
t.Fatal("Expected data to be returned")
|
||||
}
|
||||
|
|
@ -708,7 +708,7 @@ func TestGetData_FromPayload(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetData_Priority(t *testing.T) {
|
||||
func TestGetMetadata_Priority(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
queryData := map[string]interface{}{
|
||||
|
|
@ -718,8 +718,8 @@ func TestGetData_Priority(t *testing.T) {
|
|||
|
||||
headerDataBase64 := "eyJzb3VyY2UiOiJoZWFkZXIifQ==" // base64 of {"source":"header"}
|
||||
|
||||
req := httptest.NewRequest("GET", "/chat/completions?yao_data="+string(queryDataJSON), nil)
|
||||
req.Header.Set("X-Yao-Data", headerDataBase64)
|
||||
req := httptest.NewRequest("GET", "/chat/completions?metadata="+string(queryDataJSON), nil)
|
||||
req.Header.Set("X-Yao-Metadata", headerDataBase64)
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = req
|
||||
|
|
@ -729,10 +729,10 @@ func TestGetData_Priority(t *testing.T) {
|
|||
}
|
||||
|
||||
completionReq := &CompletionRequest{
|
||||
Data: payloadData,
|
||||
Metadata: payloadData,
|
||||
}
|
||||
|
||||
result := GetData(c, completionReq)
|
||||
result := GetMetadata(c, completionReq)
|
||||
if result == nil {
|
||||
t.Fatal("Expected data to be returned")
|
||||
}
|
||||
|
|
@ -742,7 +742,7 @@ func TestGetData_Priority(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetData_EmptyData(t *testing.T) {
|
||||
func TestGetMetadata_EmptyData(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
req := httptest.NewRequest("GET", "/chat/completions", nil)
|
||||
|
|
@ -750,7 +750,7 @@ func TestGetData_EmptyData(t *testing.T) {
|
|||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = req
|
||||
|
||||
result := GetData(c, nil)
|
||||
result := GetMetadata(c, nil)
|
||||
if result != nil {
|
||||
t.Errorf("Expected nil data, got '%v'", result)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,8 +149,8 @@ type Context struct {
|
|||
Accept Accept `json:"accept,omitempty"` // Response format: standard, cui-web, cui-native, cui-desktop
|
||||
|
||||
// CUI Context information
|
||||
Route string `json:"yao_route,omitempty"` // The route of the request, it will be used to identify the route of the request
|
||||
Data map[string]interface{} `json:"yao_data,omitempty"` // The data of the request, it will be used to pass data to the page
|
||||
Route string `json:"route,omitempty"` // The route of the request, it will be used to identify the route of the request
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"` // The metadata of the request, it will be used to pass data to the page
|
||||
|
||||
Silent bool `json:"silent,omitempty"` // Silent mode (Deprecated, use Referer instead)
|
||||
}
|
||||
|
|
@ -204,9 +204,6 @@ type HookCreateResponse struct {
|
|||
Temperature *float64 `json:"temperature,omitempty"`
|
||||
MaxTokens *int `json:"max_tokens,omitempty"`
|
||||
MaxCompletionTokens *int `json:"max_completion_tokens,omitempty"`
|
||||
|
||||
// Request metadata
|
||||
Metadata map[string]string `json:"metadata,omitempty"` // Optional: developer-defined tags and values for tracking requests
|
||||
}
|
||||
|
||||
// ResponseHookDone the response of the done hook
|
||||
|
|
@ -335,12 +332,9 @@ type CompletionRequest struct {
|
|||
Stream *bool `json:"stream,omitempty"` // Optional: if true, stream partial message deltas
|
||||
StreamOptions *StreamOptions `json:"stream_options,omitempty"` // Optional: options for streaming response
|
||||
|
||||
// Request metadata
|
||||
Metadata map[string]string `json:"metadata,omitempty"` // Optional: developer-defined tags and values for tracking requests
|
||||
|
||||
// CUI Context information
|
||||
Route string `json:"yao_route,omitempty"` // Optional: route of the request for CUI context
|
||||
Data map[string]interface{} `json:"yao_data,omitempty"` // Optional: data to pass to the page for CUI context
|
||||
Route string `json:"route,omitempty"` // Optional: route of the request for CUI context
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"` // Optional: metadata to pass to the page for CUI context
|
||||
}
|
||||
|
||||
// AudioConfig represents the audio output configuration for models that support audio
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue