diff --git a/backend/modules/evaluation/domain/service/expt_result_impl.go b/backend/modules/evaluation/domain/service/expt_result_impl.go index ac8a7698f..e9150b7e5 100644 --- a/backend/modules/evaluation/domain/service/expt_result_impl.go +++ b/backend/modules/evaluation/domain/service/expt_result_impl.go @@ -1287,6 +1287,37 @@ func (e *ExptResultBuilder) buildEvaluatorResult(ctx context.Context) error { logs.CtxWarn(ctx, "turnEvaluatorResultRef not found, evaluatorRecordID: %v, turnResultID: %v", evaluatorRecord.ID, turnResultID) continue } + + // 当 FullTrajectory=false 时,如果评估器输入里的 input_fields / evaluate_target_output_fields 中包含 trajectory, + // 同样做一次 JSON 预览剪裁 + 文本长度剪裁,避免评估器输入里携带超长轨迹。 + if !e.FullTrajectory && evaluatorRecord.EvaluatorInputData != nil { + // 1) InputFields 中的 trajectory + if evaluatorRecord.EvaluatorInputData.InputFields != nil { + if trajectoryContent, ok := evaluatorRecord.EvaluatorInputData.InputFields[consts.EvalTargetOutputFieldKeyTrajectory]; ok && trajectoryContent != nil { + if trajectoryContent.Text != nil && len(*trajectoryContent.Text) > 0 { + preview := utils.GenerateJsonObjectPreview(*trajectoryContent.Text) + if preview != "" { + trajectoryContent.Text = gptr.Of(utils.GenerateTextPreview(preview)) + } else { + trajectoryContent.Text = gptr.Of(utils.GenerateTextPreview(*trajectoryContent.Text)) + } + } + } + } + // 2) EvaluateTargetOutputFields 中的 trajectory + if evaluatorRecord.EvaluatorInputData.EvaluateTargetOutputFields != nil { + if trajectoryContent, ok := evaluatorRecord.EvaluatorInputData.EvaluateTargetOutputFields[consts.EvalTargetOutputFieldKeyTrajectory]; ok && trajectoryContent != nil { + if trajectoryContent.Text != nil && len(*trajectoryContent.Text) > 0 { + preview := utils.GenerateJsonObjectPreview(*trajectoryContent.Text) + if preview != "" { + trajectoryContent.Text = gptr.Of(utils.GenerateTextPreview(preview)) + } else { + trajectoryContent.Text = gptr.Of(utils.GenerateTextPreview(*trajectoryContent.Text)) + } + } + } + } + } if _, ok := turnResultID2VersionID2Result[turnResultID]; !ok { turnResultID2VersionID2Result[turnResultID] = make(map[int64]*entity.EvaluatorRecord) } @@ -1517,7 +1548,6 @@ func (e *ExptResultBuilder) buildTargetOutput(ctx context.Context) error { if !ok { continue } - // 如果不需要完整轨迹,则使用 generateJsonObjectPreview 对 trajectory 进行剪裁 if !e.FullTrajectory && targetRecord.EvalTargetOutputData != nil && @@ -1525,9 +1555,11 @@ func (e *ExptResultBuilder) buildTargetOutput(ctx context.Context) error { if trajectoryContent, ok := targetRecord.EvalTargetOutputData.OutputFields[consts.EvalTargetOutputFieldKeyTrajectory]; ok && trajectoryContent != nil { if trajectoryContent.Text != nil && len(*trajectoryContent.Text) > 0 { // 使用 generateJsonObjectPreview 对 trajectory JSON 进行剪裁 - preview := utils.GenerateJsonObjectPreview([]byte(*trajectoryContent.Text)) + preview := utils.GenerateJsonObjectPreview(*trajectoryContent.Text) if preview != "" { - trajectoryContent.Text = &preview + trajectoryContent.Text = gptr.Of(utils.GenerateTextPreview(preview)) + } else { + trajectoryContent.Text = gptr.Of(utils.GenerateTextPreview(*trajectoryContent.Text)) } } } diff --git a/backend/modules/evaluation/domain/service/expt_result_impl_test.go b/backend/modules/evaluation/domain/service/expt_result_impl_test.go index 854a1c010..110f31978 100644 --- a/backend/modules/evaluation/domain/service/expt_result_impl_test.go +++ b/backend/modules/evaluation/domain/service/expt_result_impl_test.go @@ -4000,7 +4000,7 @@ func TestExptResultBuilder_buildTargetOutput(t *testing.T) { assert.NotNil(t, trajectoryContent.Text) // 验证内容已被剪裁(使用 generateJsonObjectPreview) originalJSON := `{"id":"trace-1","root_step":{"step_id":"step-1","type":"tool_call","content":"very long content that should be trimmed"}}` - expectedPreview := utils.GenerateJsonObjectPreview([]byte(originalJSON)) + expectedPreview := utils.GenerateJsonObjectPreview(originalJSON) assert.Equal(t, expectedPreview, *trajectoryContent.Text, "trajectory should be trimmed using generateJsonObjectPreview") // actual_output 字段应该保留 _, hasActualOutput := targetOutput.EvalTargetRecord.EvalTargetOutputData.OutputFields["actual_output"] @@ -4230,3 +4230,449 @@ func TestExptResultBuilder_buildTargetOutput(t *testing.T) { }) } } + +func TestExptResultBuilder_buildEvaluatorResult(t *testing.T) { + tests := []struct { + name string + fullTrajectory bool + setup func(ctrl *gomock.Controller) (*ExptResultBuilder, *repoMocks.MockIExptTurnResultRepo, *svcMocks.MockEvaluatorRecordService) + wantErr bool + checkFunc func(t *testing.T, builder *ExptResultBuilder) + }{ + { + name: "FullTrajectory=false should trim trajectory in InputFields", + fullTrajectory: false, + setup: func(ctrl *gomock.Controller) (*ExptResultBuilder, *repoMocks.MockIExptTurnResultRepo, *svcMocks.MockEvaluatorRecordService) { + mockExptTurnResultRepo := repoMocks.NewMockIExptTurnResultRepo(ctrl) + mockEvaluatorRecordService := svcMocks.NewMockEvaluatorRecordService(ctrl) + + fullTrajectoryJSON := `{"id":"trace-1","root_step":{"step_id":"step-1","type":"tool_call","content":"very long content that should be trimmed"}}` + + builder := &ExptResultBuilder{ + exptDO: &entity.Experiment{ + ID: 1, + ExptType: entity.ExptType_Offline, + }, + SpaceID: 100, + turnResultDO: []*entity.ExptTurnResult{ + { + ID: 10, + }, + }, + ExptTurnResultRepo: mockExptTurnResultRepo, + evaluatorRecordService: mockEvaluatorRecordService, + FullTrajectory: false, + } + + mockExptTurnResultRepo.EXPECT(). + BatchGetTurnEvaluatorResultRef(gomock.Any(), int64(100), []int64{10}). + Return([]*entity.ExptTurnEvaluatorResultRef{ + {ExptTurnResultID: 10, EvaluatorResultID: 1001, EvaluatorVersionID: 201}, + }, nil) + + mockEvaluatorRecordService.EXPECT(). + BatchGetEvaluatorRecord(gomock.Any(), []int64{1001}, false). + Return([]*entity.EvaluatorRecord{ + { + ID: 1001, + EvaluatorVersionID: 201, + EvaluatorInputData: &entity.EvaluatorInputData{ + InputFields: map[string]*entity.Content{ + consts.EvalTargetOutputFieldKeyTrajectory: { + Text: gptr.Of(fullTrajectoryJSON), + }, + }, + }, + }, + }, nil) + + return builder, mockExptTurnResultRepo, mockEvaluatorRecordService + }, + wantErr: false, + checkFunc: func(t *testing.T, builder *ExptResultBuilder) { + assert.NotNil(t, builder.turnResultID2EvaluatorVersionID2Result) + evaluatorRecords, ok := builder.turnResultID2EvaluatorVersionID2Result[10] + assert.True(t, ok) + assert.NotNil(t, evaluatorRecords) + evaluatorRecord, ok := evaluatorRecords[201] + assert.True(t, ok) + assert.NotNil(t, evaluatorRecord) + assert.NotNil(t, evaluatorRecord.EvaluatorInputData) + assert.NotNil(t, evaluatorRecord.EvaluatorInputData.InputFields) + + trajectoryContent, hasTrajectory := evaluatorRecord.EvaluatorInputData.InputFields[consts.EvalTargetOutputFieldKeyTrajectory] + assert.True(t, hasTrajectory, "trajectory field should exist") + assert.NotNil(t, trajectoryContent) + assert.NotNil(t, trajectoryContent.Text) + + // 验证内容已被剪裁 + originalJSON := `{"id":"trace-1","root_step":{"step_id":"step-1","type":"tool_call","content":"very long content that should be trimmed"}}` + expectedPreview := utils.GenerateJsonObjectPreview(originalJSON) + expectedTrimmed := utils.GenerateTextPreview(expectedPreview) + assert.Equal(t, expectedTrimmed, *trajectoryContent.Text, "trajectory should be trimmed") + }, + }, + { + name: "FullTrajectory=false should trim trajectory in EvaluateTargetOutputFields", + fullTrajectory: false, + setup: func(ctrl *gomock.Controller) (*ExptResultBuilder, *repoMocks.MockIExptTurnResultRepo, *svcMocks.MockEvaluatorRecordService) { + mockExptTurnResultRepo := repoMocks.NewMockIExptTurnResultRepo(ctrl) + mockEvaluatorRecordService := svcMocks.NewMockEvaluatorRecordService(ctrl) + + fullTrajectoryJSON := `{"id":"trace-2","root_step":{"step_id":"step-2","type":"message","content":"another very long content"}}` + + builder := &ExptResultBuilder{ + exptDO: &entity.Experiment{ + ID: 1, + ExptType: entity.ExptType_Offline, + }, + SpaceID: 100, + turnResultDO: []*entity.ExptTurnResult{ + { + ID: 10, + }, + }, + ExptTurnResultRepo: mockExptTurnResultRepo, + evaluatorRecordService: mockEvaluatorRecordService, + FullTrajectory: false, + } + + mockExptTurnResultRepo.EXPECT(). + BatchGetTurnEvaluatorResultRef(gomock.Any(), int64(100), []int64{10}). + Return([]*entity.ExptTurnEvaluatorResultRef{ + {ExptTurnResultID: 10, EvaluatorResultID: 1001, EvaluatorVersionID: 201}, + }, nil) + + mockEvaluatorRecordService.EXPECT(). + BatchGetEvaluatorRecord(gomock.Any(), []int64{1001}, false). + Return([]*entity.EvaluatorRecord{ + { + ID: 1001, + EvaluatorVersionID: 201, + EvaluatorInputData: &entity.EvaluatorInputData{ + EvaluateTargetOutputFields: map[string]*entity.Content{ + consts.EvalTargetOutputFieldKeyTrajectory: { + Text: gptr.Of(fullTrajectoryJSON), + }, + }, + }, + }, + }, nil) + + return builder, mockExptTurnResultRepo, mockEvaluatorRecordService + }, + wantErr: false, + checkFunc: func(t *testing.T, builder *ExptResultBuilder) { + assert.NotNil(t, builder.turnResultID2EvaluatorVersionID2Result) + evaluatorRecords, ok := builder.turnResultID2EvaluatorVersionID2Result[10] + assert.True(t, ok) + assert.NotNil(t, evaluatorRecords) + evaluatorRecord, ok := evaluatorRecords[201] + assert.True(t, ok) + assert.NotNil(t, evaluatorRecord) + assert.NotNil(t, evaluatorRecord.EvaluatorInputData) + assert.NotNil(t, evaluatorRecord.EvaluatorInputData.EvaluateTargetOutputFields) + + trajectoryContent, hasTrajectory := evaluatorRecord.EvaluatorInputData.EvaluateTargetOutputFields[consts.EvalTargetOutputFieldKeyTrajectory] + assert.True(t, hasTrajectory, "trajectory field should exist") + assert.NotNil(t, trajectoryContent) + assert.NotNil(t, trajectoryContent.Text) + + // 验证内容已被剪裁 + originalJSON := `{"id":"trace-2","root_step":{"step_id":"step-2","type":"message","content":"another very long content"}}` + expectedPreview := utils.GenerateJsonObjectPreview(originalJSON) + expectedTrimmed := utils.GenerateTextPreview(expectedPreview) + assert.Equal(t, expectedTrimmed, *trajectoryContent.Text, "trajectory should be trimmed") + }, + }, + { + name: "FullTrajectory=false should trim trajectory in both InputFields and EvaluateTargetOutputFields", + fullTrajectory: false, + setup: func(ctrl *gomock.Controller) (*ExptResultBuilder, *repoMocks.MockIExptTurnResultRepo, *svcMocks.MockEvaluatorRecordService) { + mockExptTurnResultRepo := repoMocks.NewMockIExptTurnResultRepo(ctrl) + mockEvaluatorRecordService := svcMocks.NewMockEvaluatorRecordService(ctrl) + + fullTrajectoryJSON1 := `{"id":"trace-1","root_step":{"step_id":"step-1"}}` + fullTrajectoryJSON2 := `{"id":"trace-2","root_step":{"step_id":"step-2"}}` + + builder := &ExptResultBuilder{ + exptDO: &entity.Experiment{ + ID: 1, + ExptType: entity.ExptType_Offline, + }, + SpaceID: 100, + turnResultDO: []*entity.ExptTurnResult{ + { + ID: 10, + }, + }, + ExptTurnResultRepo: mockExptTurnResultRepo, + evaluatorRecordService: mockEvaluatorRecordService, + FullTrajectory: false, + } + + mockExptTurnResultRepo.EXPECT(). + BatchGetTurnEvaluatorResultRef(gomock.Any(), int64(100), []int64{10}). + Return([]*entity.ExptTurnEvaluatorResultRef{ + {ExptTurnResultID: 10, EvaluatorResultID: 1001, EvaluatorVersionID: 201}, + }, nil) + + mockEvaluatorRecordService.EXPECT(). + BatchGetEvaluatorRecord(gomock.Any(), []int64{1001}, false). + Return([]*entity.EvaluatorRecord{ + { + ID: 1001, + EvaluatorVersionID: 201, + EvaluatorInputData: &entity.EvaluatorInputData{ + InputFields: map[string]*entity.Content{ + consts.EvalTargetOutputFieldKeyTrajectory: { + Text: gptr.Of(fullTrajectoryJSON1), + }, + }, + EvaluateTargetOutputFields: map[string]*entity.Content{ + consts.EvalTargetOutputFieldKeyTrajectory: { + Text: gptr.Of(fullTrajectoryJSON2), + }, + }, + }, + }, + }, nil) + + return builder, mockExptTurnResultRepo, mockEvaluatorRecordService + }, + wantErr: false, + checkFunc: func(t *testing.T, builder *ExptResultBuilder) { + assert.NotNil(t, builder.turnResultID2EvaluatorVersionID2Result) + evaluatorRecords, ok := builder.turnResultID2EvaluatorVersionID2Result[10] + assert.True(t, ok) + assert.NotNil(t, evaluatorRecords) + evaluatorRecord, ok := evaluatorRecords[201] + assert.True(t, ok) + assert.NotNil(t, evaluatorRecord) + + // 验证 InputFields 中的 trajectory 被剪裁 + trajectoryContent1, hasTrajectory1 := evaluatorRecord.EvaluatorInputData.InputFields[consts.EvalTargetOutputFieldKeyTrajectory] + assert.True(t, hasTrajectory1) + assert.NotNil(t, trajectoryContent1.Text) + expectedPreview1 := utils.GenerateJsonObjectPreview(`{"id":"trace-1","root_step":{"step_id":"step-1"}}`) + expectedTrimmed1 := utils.GenerateTextPreview(expectedPreview1) + assert.Equal(t, expectedTrimmed1, *trajectoryContent1.Text) + + // 验证 EvaluateTargetOutputFields 中的 trajectory 被剪裁 + trajectoryContent2, hasTrajectory2 := evaluatorRecord.EvaluatorInputData.EvaluateTargetOutputFields[consts.EvalTargetOutputFieldKeyTrajectory] + assert.True(t, hasTrajectory2) + assert.NotNil(t, trajectoryContent2.Text) + expectedPreview2 := utils.GenerateJsonObjectPreview(`{"id":"trace-2","root_step":{"step_id":"step-2"}}`) + expectedTrimmed2 := utils.GenerateTextPreview(expectedPreview2) + assert.Equal(t, expectedTrimmed2, *trajectoryContent2.Text) + }, + }, + { + name: "FullTrajectory=true should preserve trajectory", + fullTrajectory: true, + setup: func(ctrl *gomock.Controller) (*ExptResultBuilder, *repoMocks.MockIExptTurnResultRepo, *svcMocks.MockEvaluatorRecordService) { + mockExptTurnResultRepo := repoMocks.NewMockIExptTurnResultRepo(ctrl) + mockEvaluatorRecordService := svcMocks.NewMockEvaluatorRecordService(ctrl) + + fullTrajectoryJSON := `{"id":"trace-1","root_step":{"step_id":"step-1"}}` + + builder := &ExptResultBuilder{ + exptDO: &entity.Experiment{ + ID: 1, + ExptType: entity.ExptType_Offline, + }, + SpaceID: 100, + turnResultDO: []*entity.ExptTurnResult{ + { + ID: 10, + }, + }, + ExptTurnResultRepo: mockExptTurnResultRepo, + evaluatorRecordService: mockEvaluatorRecordService, + FullTrajectory: true, + } + + mockExptTurnResultRepo.EXPECT(). + BatchGetTurnEvaluatorResultRef(gomock.Any(), int64(100), []int64{10}). + Return([]*entity.ExptTurnEvaluatorResultRef{ + {ExptTurnResultID: 10, EvaluatorResultID: 1001, EvaluatorVersionID: 201}, + }, nil) + + mockEvaluatorRecordService.EXPECT(). + BatchGetEvaluatorRecord(gomock.Any(), []int64{1001}, false). + Return([]*entity.EvaluatorRecord{ + { + ID: 1001, + EvaluatorVersionID: 201, + EvaluatorInputData: &entity.EvaluatorInputData{ + InputFields: map[string]*entity.Content{ + consts.EvalTargetOutputFieldKeyTrajectory: { + Text: gptr.Of(fullTrajectoryJSON), + }, + }, + }, + }, + }, nil) + + return builder, mockExptTurnResultRepo, mockEvaluatorRecordService + }, + wantErr: false, + checkFunc: func(t *testing.T, builder *ExptResultBuilder) { + assert.NotNil(t, builder.turnResultID2EvaluatorVersionID2Result) + evaluatorRecords, ok := builder.turnResultID2EvaluatorVersionID2Result[10] + assert.True(t, ok) + assert.NotNil(t, evaluatorRecords) + evaluatorRecord, ok := evaluatorRecords[201] + assert.True(t, ok) + assert.NotNil(t, evaluatorRecord) + + // 验证 trajectory 未被剪裁(保持原样) + trajectoryContent, hasTrajectory := evaluatorRecord.EvaluatorInputData.InputFields[consts.EvalTargetOutputFieldKeyTrajectory] + assert.True(t, hasTrajectory) + assert.NotNil(t, trajectoryContent) + assert.Equal(t, `{"id":"trace-1","root_step":{"step_id":"step-1"}}`, *trajectoryContent.Text, "trajectory should be preserved when FullTrajectory=true") + }, + }, + { + name: "FullTrajectory=false with invalid JSON should use GenerateTextPreview directly", + fullTrajectory: false, + setup: func(ctrl *gomock.Controller) (*ExptResultBuilder, *repoMocks.MockIExptTurnResultRepo, *svcMocks.MockEvaluatorRecordService) { + mockExptTurnResultRepo := repoMocks.NewMockIExptTurnResultRepo(ctrl) + mockEvaluatorRecordService := svcMocks.NewMockEvaluatorRecordService(ctrl) + + invalidJSON := `"not a json object"` + + builder := &ExptResultBuilder{ + exptDO: &entity.Experiment{ + ID: 1, + ExptType: entity.ExptType_Offline, + }, + SpaceID: 100, + turnResultDO: []*entity.ExptTurnResult{ + { + ID: 10, + }, + }, + ExptTurnResultRepo: mockExptTurnResultRepo, + evaluatorRecordService: mockEvaluatorRecordService, + FullTrajectory: false, + } + + mockExptTurnResultRepo.EXPECT(). + BatchGetTurnEvaluatorResultRef(gomock.Any(), int64(100), []int64{10}). + Return([]*entity.ExptTurnEvaluatorResultRef{ + {ExptTurnResultID: 10, EvaluatorResultID: 1001, EvaluatorVersionID: 201}, + }, nil) + + mockEvaluatorRecordService.EXPECT(). + BatchGetEvaluatorRecord(gomock.Any(), []int64{1001}, false). + Return([]*entity.EvaluatorRecord{ + { + ID: 1001, + EvaluatorVersionID: 201, + EvaluatorInputData: &entity.EvaluatorInputData{ + InputFields: map[string]*entity.Content{ + consts.EvalTargetOutputFieldKeyTrajectory: { + Text: gptr.Of(invalidJSON), + }, + }, + }, + }, + }, nil) + + return builder, mockExptTurnResultRepo, mockEvaluatorRecordService + }, + wantErr: false, + checkFunc: func(t *testing.T, builder *ExptResultBuilder) { + assert.NotNil(t, builder.turnResultID2EvaluatorVersionID2Result) + evaluatorRecords, ok := builder.turnResultID2EvaluatorVersionID2Result[10] + assert.True(t, ok) + assert.NotNil(t, evaluatorRecords) + evaluatorRecord, ok := evaluatorRecords[201] + assert.True(t, ok) + assert.NotNil(t, evaluatorRecord) + + // 验证无效 JSON 时直接使用 GenerateTextPreview + trajectoryContent, hasTrajectory := evaluatorRecord.EvaluatorInputData.InputFields[consts.EvalTargetOutputFieldKeyTrajectory] + assert.True(t, hasTrajectory) + assert.NotNil(t, trajectoryContent) + expectedTrimmed := utils.GenerateTextPreview(`"not a json object"`) + assert.Equal(t, expectedTrimmed, *trajectoryContent.Text, "invalid JSON should be trimmed using GenerateTextPreview directly") + }, + }, + { + name: "FullTrajectory=false with nil InputFields should not panic", + fullTrajectory: false, + setup: func(ctrl *gomock.Controller) (*ExptResultBuilder, *repoMocks.MockIExptTurnResultRepo, *svcMocks.MockEvaluatorRecordService) { + mockExptTurnResultRepo := repoMocks.NewMockIExptTurnResultRepo(ctrl) + mockEvaluatorRecordService := svcMocks.NewMockEvaluatorRecordService(ctrl) + + builder := &ExptResultBuilder{ + exptDO: &entity.Experiment{ + ID: 1, + ExptType: entity.ExptType_Offline, + }, + SpaceID: 100, + turnResultDO: []*entity.ExptTurnResult{ + { + ID: 10, + }, + }, + ExptTurnResultRepo: mockExptTurnResultRepo, + evaluatorRecordService: mockEvaluatorRecordService, + FullTrajectory: false, + } + + mockExptTurnResultRepo.EXPECT(). + BatchGetTurnEvaluatorResultRef(gomock.Any(), int64(100), []int64{10}). + Return([]*entity.ExptTurnEvaluatorResultRef{ + {ExptTurnResultID: 10, EvaluatorResultID: 1001, EvaluatorVersionID: 201}, + }, nil) + + mockEvaluatorRecordService.EXPECT(). + BatchGetEvaluatorRecord(gomock.Any(), []int64{1001}, false). + Return([]*entity.EvaluatorRecord{ + { + ID: 1001, + EvaluatorVersionID: 201, + EvaluatorInputData: &entity.EvaluatorInputData{ + InputFields: nil, + }, + }, + }, nil) + + return builder, mockExptTurnResultRepo, mockEvaluatorRecordService + }, + wantErr: false, + checkFunc: func(t *testing.T, builder *ExptResultBuilder) { + assert.NotNil(t, builder.turnResultID2EvaluatorVersionID2Result) + evaluatorRecords, ok := builder.turnResultID2EvaluatorVersionID2Result[10] + assert.True(t, ok) + assert.NotNil(t, evaluatorRecords) + evaluatorRecord, ok := evaluatorRecords[201] + assert.True(t, ok) + assert.NotNil(t, evaluatorRecord) + assert.Nil(t, evaluatorRecord.EvaluatorInputData.InputFields) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + builder, _, _ := tt.setup(ctrl) + err := builder.buildEvaluatorResult(context.Background()) + + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + if tt.checkFunc != nil { + tt.checkFunc(t, builder) + } + } + }) + } +} diff --git a/backend/modules/evaluation/pkg/utils/trim.go b/backend/modules/evaluation/pkg/utils/trim.go index 6bcfbd267..adca813a8 100644 --- a/backend/modules/evaluation/pkg/utils/trim.go +++ b/backend/modules/evaluation/pkg/utils/trim.go @@ -1,16 +1,5 @@ -// Copyright 2026 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Copyright (c) 2025 coze-dev Authors +// SPDX-License-Identifier: Apache-2.0 package utils @@ -20,9 +9,9 @@ import ( // countJsonArrayElements 计算 json array 中包含的元素数量 // notice: 出于性能考虑,直接对字符串计算元素数量. 当 array string 非法时会直接返回 0 -func countJsonArrayElements(jsonBytes []byte) int { - jsonStr := strings.TrimSpace(string(jsonBytes)) - if len(strings.TrimSpace(jsonStr)) <= 2 || !strings.HasPrefix(jsonStr, "[") || !strings.HasSuffix(jsonStr, "]") { +func countJsonArrayElements(jsonStr string) int { + jsonStr = strings.TrimSpace(jsonStr) + if len(jsonStr) <= 2 || !strings.HasPrefix(jsonStr, "[") || !strings.HasSuffix(jsonStr, "]") { return 0 } var ( @@ -67,8 +56,8 @@ func countJsonArrayElements(jsonBytes []byte) int { // notice: 出于性能考虑,直接根据字符串生成. // * 对于字符串、数值等简单类型,仅保留前五位作为预览内容 // * 对于object、array 等复杂类型,不递归解析,直接展示为 "{...}" 或 "[...]" -func GenerateJsonObjectPreview(jsonBytes []byte) string { - jsonStr := strings.TrimSpace(string(jsonBytes)) +func GenerateJsonObjectPreview(jsonStr string) string { + jsonStr = strings.TrimSpace(jsonStr) if len(jsonStr) < 2 || jsonStr[0] != '{' || jsonStr[len(jsonStr)-1] != '}' { return "" } @@ -151,12 +140,12 @@ func summarizeValue(value string) string { } } -// generateTextPreview 生成文本类型的预览内容 -func generateTextPreview(content []byte) string { +// GenerateTextPreview 生成文本类型的预览内容 +func GenerateTextPreview(content string) string { const previewContentLength = 100 - runes := []rune(string(content)) + runes := []rune(content) if len(runes) <= previewContentLength { - return string(content) + return content } return string(runes[:previewContentLength]) + "..." } diff --git a/backend/modules/evaluation/pkg/utils/trim_test.go b/backend/modules/evaluation/pkg/utils/trim_test.go index d47372d4d..259a42988 100644 --- a/backend/modules/evaluation/pkg/utils/trim_test.go +++ b/backend/modules/evaluation/pkg/utils/trim_test.go @@ -73,7 +73,7 @@ func TestCountJsonArrayElements(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - got := countJsonArrayElements([]byte(tt.input)) + got := countJsonArrayElements(tt.input) assert.Equal(t, tt.expected, got) }) } @@ -118,7 +118,7 @@ func TestGenerateJsonObjectPreview(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - got := GenerateJsonObjectPreview([]byte(tt.input)) + got := GenerateJsonObjectPreview(tt.input) assert.Equal(t, tt.expected, got) }) } @@ -199,7 +199,7 @@ func TestGenerateTextPreview(t *testing.T) { }, { name: "long ascii content should be trimmed", - input: string(make([]byte, 120)), + input: string(make([]byte, 200)), }, { name: "utf8 content shorter than limit should not be trimmed", @@ -212,12 +212,12 @@ func TestGenerateTextPreview(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - got := generateTextPreview([]byte(tt.input)) + got := GenerateTextPreview(tt.input) switch tt.name { case "long ascii content should be trimmed": assert.Len(t, []rune(got), 103) // 100 chars + "..." - assert.Equal(t, "...", got[len(got)-3:]) + assert.Equal(t, "...", string([]rune(got)[100:])) default: assert.Equal(t, tt.expected, got) }