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:
Max 2025-11-14 08:41:40 +08:00
parent e0f69368fb
commit aa69b693e3
7 changed files with 420 additions and 108 deletions

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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