feat(audit): capture and play back transcription input audio#365
Conversation
When LOGGING_LOG_AUDIO_BODIES is enabled, /v1/audio/transcriptions now stores the uploaded audio losslessly as base64 (same 8 MB cap and too_large fallback as speech output) alongside the existing upload metadata, so the dashboard renders a player for the request body — matching the playback already available for speech responses. - Resolve the upload's content type from the multipart part, falling back to the filename extension (capture FileContentType on the transcription request). - Add AudioBodyLog.Meta + BuildAudioUploadBody so the audio and its parameters are recorded together; the dashboard lists the metadata under the player. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughCaptures uploaded transcription audio (optionally base64, ≤8 MB) and upload metadata into audit entries when enabled; refactors audio-body construction, records file Content-Type, normalizes MIME type from headers or filename, renders metadata in the dashboard, and updates docs and tests. ChangesAudio Metadata Audit Logging for Transcriptions
Sequence Diagram(s)sequenceDiagram
participant Client
participant Server
participant AuditLog
participant Dashboard
Client->>Server: POST /v1/audio/transcriptions (multipart file + fields)
Server->>Server: transcriptionRequestFromForm reads part Content-Type into FileContentType
Server->>Server: audioUploadContentType(normalize MIME or infer from filename)
Server->>AuditLog: BuildAudioUploadBody(contentType, bytes, storeBytes, meta)
AuditLog->>AuditLog: store `AudioBodyLog` (meta + base64 bytes or placeholder)
Dashboard->>AuditLog: request audit entry
AuditLog->>Dashboard: return AudioBodyLog (includes meta and playable bytes if stored)
Dashboard->>Client: render audio player + metadata
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
Greptile SummaryThis PR makes transcription audio audit logging playable in the dashboard. It changes:
Confidence Score: 5/5This looks safe to merge.
Reviews (2): Last reviewed commit: "fix(audit): preserve transcription metad..." | Re-trigger Greptile |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/server/audio_service.go`:
- Around line 232-235: audioUploadContentType currently returns the raw
FileContentType (e.g. "audio/webm; codecs=opus") which then fails later in
renderAudioBody; update audioUploadContentType to parse and persist only the
base MIME type (strip any parameters after ';') — e.g. trim whitespace, use
mime.ParseMediaType or split on ';' and take the first token, validate with
auditlog.IsAudioContentType on that base type, and return that canonical type so
renderAudioBody and dashboard data URLs receive a clean MIME like "audio/webm".
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: c041ce26-0a77-40e5-9b93-0f4040baeab6
📒 Files selected for processing (11)
CLAUDE.mddocs/advanced/audio-api.mdxdocs/advanced/configuration.mdxinternal/admin/dashboard/static/css/dashboard.cssinternal/admin/dashboard/static/js/modules/audit-list.test.cjsinternal/admin/dashboard/static/js/modules/conversation-helpers.jsinternal/auditlog/audio_body.gointernal/auditlog/audio_body_test.gointernal/core/audio.gointernal/server/audio_service.gointernal/server/audio_service_test.go
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
… normalize upload MIME Address PR review: - Gate transcription request capture on LogBodies (the master switch) rather than requiring LogAudioBodies too. Metadata (model, filename, language, params) is now recorded whenever body logging is on; LogAudioBodies only controls whether the raw audio is embedded as base64 vs a metadata-only placeholder — mirroring the speech-response behavior, so keeping raw audio off no longer drops the params. - Strip MIME parameters from the uploaded part's Content-Type (e.g. "audio/webm; codecs=opus" -> "audio/webm") so the stored type is a bare media type the dashboard accepts; a parameterized type previously fell back to audio/mpeg and broke playback for wav/ogg/webm uploads. Add a content-type resolver unit test and update the gating tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
internal/server/audio_service_test.go (1)
232-249: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winAdd one handler-level test for multipart
Content-Typepropagation.These tests only cover extension fallback and the normalization helper in isolation. They do not verify that
transcriptionRequestFromForm()actually copies the file part header intocore.AudioTranscriptionRequest.FileContentType, so a regression on Line 180 ininternal/server/audio_service.gowould still pass. Please add an end-to-end case that builds a multipart part with an explicit header such asaudio/webm; codecs=opus, callsCreateTranscription, and asserts both the captured request field and the normalized auditContentType.Suggested test shape
+func TestAudioTranscription_UsesMultipartFileContentTypeForAuditBody(t *testing.T) { + svc := &audioService{provider: newTranscriptionMock(), logBodies: true, logAudioBodies: true} + + var buf bytes.Buffer + w := multipart.NewWriter(&buf) + _ = w.WriteField("model", "gpt-4o-transcribe") + h := make(textproto.MIMEHeader) + h.Set("Content-Disposition", `form-data; name="file"; filename="speech.bin"`) + h.Set("Content-Type", "audio/webm; codecs=opus") + part, err := w.CreatePart(h) + if err != nil { + t.Fatalf("CreatePart: %v", err) + } + _, _ = part.Write([]byte("uploaded-audio-bytes")) + _ = w.Close() + + req := httptest.NewRequest(http.MethodPost, "/v1/audio/transcriptions", &buf) + req.Header.Set("Content-Type", w.FormDataContentType()) + rec := httptest.NewRecorder() + c := echo.New().NewContext(req, rec) + entry := &auditlog.LogEntry{} + c.Set(string(auditlog.LogEntryKey), entry) + + if err := svc.CreateTranscription(c); err != nil { + t.Fatalf("CreateTranscription returned error: %v", err) + } + + if got := svc.provider.(*audioMockProvider).capturedTranscription.FileContentType; got != "audio/webm; codecs=opus" { + t.Fatalf("FileContentType = %q", got) + } + body := entry.Data.RequestBody.(auditlog.AudioBodyLog) + if body.ContentType != "audio/webm" { + t.Fatalf("audit content type = %q, want audio/webm", body.ContentType) + } +}As per coding guidelines,
**/*_test.go: "Add or update tests for behavior changes. Tests should cover request translation, response normalization, error handling, default configuration, and provider-specific parameter mapping."Also applies to: 263-347
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/server/audio_service_test.go` around lines 232 - 249, Add a handler-level test that ensures multipart file part headers propagate into the created transcription request and the audit entry: update or add to the tests that call newTranscriptionRequestWithAuditEntry (or create a variant) to build a multipart part with an explicit Content-Type like "audio/webm; codecs=opus", invoke the server handler (CreateTranscription) so transcriptionRequestFromForm runs, then assert that the returned/recorded core.AudioTranscriptionRequest.FileContentType equals the explicit header and that the auditlog.LogEntry stored on the context has its ContentType normalized/recorded accordingly; reference newTranscriptionRequestWithAuditEntry, CreateTranscription, transcriptionRequestFromForm, core.AudioTranscriptionRequest.FileContentType and auditlog.LogEntry when locating where to add the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@internal/server/audio_service_test.go`:
- Around line 232-249: Add a handler-level test that ensures multipart file part
headers propagate into the created transcription request and the audit entry:
update or add to the tests that call newTranscriptionRequestWithAuditEntry (or
create a variant) to build a multipart part with an explicit Content-Type like
"audio/webm; codecs=opus", invoke the server handler (CreateTranscription) so
transcriptionRequestFromForm runs, then assert that the returned/recorded
core.AudioTranscriptionRequest.FileContentType equals the explicit header and
that the auditlog.LogEntry stored on the context has its ContentType
normalized/recorded accordingly; reference
newTranscriptionRequestWithAuditEntry, CreateTranscription,
transcriptionRequestFromForm, core.AudioTranscriptionRequest.FileContentType and
auditlog.LogEntry when locating where to add the test.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 585fe64f-5bd4-48de-be89-5145d8221926
📒 Files selected for processing (3)
docs/advanced/configuration.mdxinternal/server/audio_service.gointernal/server/audio_service_test.go
Summary
Makes audio audit logging symmetric. Previously, with
LOGGING_LOG_AUDIO_BODIESenabled, only the output of/v1/audio/speechwas stored as playable base64; the input audio uploaded to/v1/audio/transcriptionswas recorded as metadata only. Now the uploaded audio is captured too, so the dashboard renders a player in the Request pane.Behavior
When
LOGGING_LOG_AUDIO_BODIESis on (andLOGGING_LOG_BODIES, the master switch, is on):/v1/audio/transcriptionsstores the uploaded audio losslessly as base64 — same 8 MB cap andtoo_largefallback as speech output — alongside the existing upload metadata (model, filename, language, params).<audio>player for the request body, with the upload parameters listed beneath it.When the flag is off, the upload is not captured (unchanged).
Implementation
internal/core/audio.go: captureFileContentTypeon the transcription request.internal/server/audio_service.go: embed the upload viaBuildAudioUploadBody; resolve the content type from the multipart part, falling back to the filename extension (audioUploadContentType).internal/auditlog/audio_body.go: addAudioBodyLog.MetaandBuildAudioUploadBody(shared builder withBuildAudioResponseBody) so the audio and its parameters are recorded together.internal/admin/dashboard:renderAudioBodylists attached metadata below the player; CSS for the metadata block.Testing
BuildAudioUploadBodyunit tests (base64 + meta, placeholder keeps meta); service tests that the transcription request body is a stored, playableAudioBodyLogwith metadata when enabled, and absent when disabled.renderAudioBodyrenders player + metadata rows (and metadata even whentoo_large). 32/32 dashboard tests pass./v1/audio/transcriptionsis stored and decodes byte-identical; content type resolves toaudio/mpegfrom the.mp3extension; metadata (model,language) captured.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Tests