(t *testing.T)
| 566 | } |
| 567 | |
| 568 | func TestDebugModel_StreamObject(t *testing.T) { |
| 569 | t.Parallel() |
| 570 | |
| 571 | ctrl := gomock.NewController(t) |
| 572 | db := dbmock.NewMockStore(ctrl) |
| 573 | chatID := uuid.New() |
| 574 | ownerID := uuid.New() |
| 575 | runID := uuid.New() |
| 576 | parts := []fantasy.ObjectStreamPart{ |
| 577 | {Type: fantasy.ObjectStreamPartTypeTextDelta, Delta: "ob"}, |
| 578 | {Type: fantasy.ObjectStreamPartTypeTextDelta, Delta: "ject"}, |
| 579 | {Type: fantasy.ObjectStreamPartTypeObject, Object: map[string]any{"value": "object"}}, |
| 580 | {Type: fantasy.ObjectStreamPartTypeFinish, FinishReason: fantasy.FinishReasonStop, Usage: fantasy.Usage{InputTokens: 5, OutputTokens: 2, TotalTokens: 7}}, |
| 581 | } |
| 582 | |
| 583 | expectDebugLoggingEnabled(t, db, ownerID) |
| 584 | stepID := expectCreateStep(t, db, runID, chatID, OperationStream) |
| 585 | expectUpdateStep(t, db, stepID, chatID, StatusCompleted, func(params database.UpdateChatDebugStepParams) { |
| 586 | require.True(t, params.NormalizedResponse.Valid) |
| 587 | require.True(t, params.Usage.Valid) |
| 588 | require.True(t, params.Attempts.Valid) |
| 589 | // Clean successes (no prior error) leave the error column |
| 590 | // as SQL NULL rather than sending jsonClear. |
| 591 | require.False(t, params.Error.Valid) |
| 592 | require.True(t, params.Metadata.Valid) |
| 593 | |
| 594 | // Verify usage JSON content matches the finish part. |
| 595 | var usage normalizedUsage |
| 596 | require.NoError(t, json.Unmarshal(params.Usage.RawMessage, &usage)) |
| 597 | require.EqualValues(t, 5, usage.InputTokens) |
| 598 | require.EqualValues(t, 2, usage.OutputTokens) |
| 599 | require.EqualValues(t, 7, usage.TotalTokens) |
| 600 | |
| 601 | // Verify the object response payload. |
| 602 | var resp normalizedObjectResponsePayload |
| 603 | require.NoError(t, json.Unmarshal(params.NormalizedResponse.RawMessage, &resp)) |
| 604 | require.Equal(t, "stop", resp.FinishReason) |
| 605 | require.True(t, resp.StructuredOutput) |
| 606 | // "ob" + "ject" = 6 runes. |
| 607 | require.Equal(t, 6, resp.RawTextLength) |
| 608 | |
| 609 | // Verify metadata contains structured_output flag. |
| 610 | var meta map[string]any |
| 611 | require.NoError(t, json.Unmarshal(params.Metadata.RawMessage, &meta)) |
| 612 | require.Equal(t, true, meta["structured_output"]) |
| 613 | summary, ok := meta["stream_summary"].(map[string]any) |
| 614 | require.True(t, ok, "metadata must contain stream_summary") |
| 615 | require.EqualValues(t, 2, summary["text_delta_count"]) |
| 616 | require.EqualValues(t, 1, summary["object_part_count"]) |
| 617 | }) |
| 618 | |
| 619 | svc := NewService(db, testutil.Logger(t), nil) |
| 620 | model := &debugModel{ |
| 621 | inner: &chattest.FakeModel{ |
| 622 | StreamObjectFn: func(ctx context.Context, call fantasy.ObjectCall) (fantasy.ObjectStreamResponse, error) { |
| 623 | stepCtx, ok := StepFromContext(ctx) |
| 624 | require.True(t, ok) |
| 625 | require.Equal(t, runID, stepCtx.RunID) |
nothing calls this directly
no test coverage detected