fix(proxy): record messages stream telemetry - #363

Merged
nic-6443 merged 3 commits into
mainfrom
fix/issue-359-messages-stream-telemetry
May 21, 2026
Merged

fix(proxy): record messages stream telemetry#363
nic-6443 merged 3 commits into
mainfrom
fix/issue-359-messages-stream-telemetry

Conversation

@jarvis9443

@jarvis9443jarvis9443 commented May 21, 2026

Copy link
Copy Markdown
Contributor

Fixes#359.

When an Anthropic /v1/messages streaming request is routed to an OpenAI-compatible upstream, telemetry now waits for the stream to finish before emitting usage. That lets the proxy record TTFT, token counts, provider request metadata, canonical finish-reason labels, and inbound_protocol = "anthropic" for the protocol-conversion path.

TTFT histogram samples are only recorded when the first-token measurement is known, so non-streaming/default paths do not add synthetic 0ms samples.

This keeps Anthropic-upstream byte-stream passthrough behavior unchanged.

CopilotAI review requested due to automatic review settings May 21, 2026 09:09
@coderabbitai

coderabbitaiBot commented May 21, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR adds time-to-first-token (TTFT) measurement for cross-provider streaming by threading a started timestamp through the dispatch chain. Cross-provider streaming now computes TTFT when the first content chunk arrives and emits the Anthropic usage event from the stream completion callback instead of the outer wrapper, controlled by a new usage_handled_by_stream flag on DispatchOutcome.

Changes

Cross-provider Streaming TTFT and Conditional Usage Event Emission

Layer / File(s)Summary
Data structures for TTFT and conditional emission
crates/aisix-proxy/src/messages.rs
DispatchOutcome gains usage_handled_by_stream boolean field; AnthropicUsageMetrics gains ttft_ms: u32 field; new AnthropicStreamCompletion data type and CompleteAnthropicStreamOnDrop drop-guard to ensure callback invocation.
Request entry point and timestamp threading
crates/aisix-proxy/src/messages.rs
Handler passes started: Instant to dispatch() and destructures usage_handled_by_stream from DispatchOutcome; dispatch function signature extended to accept started parameter.
Cross-provider streaming with TTFT computation and completion callback
crates/aisix-proxy/src/messages.rs
cross_provider_dispatch signature accepts started and identity fields; streaming path calls build_anthropic_sse_stream with on_complete callback that constructs AnthropicUsageMetrics with computed ttft_ms and emits usage event on stream completion; streaming outcome marks usage_handled_by_stream: true.
Conditional usage event emission and TTFT metric recording
crates/aisix-proxy/src/messages.rs
Wrapper's usage event emission is gated to run only when usage_handled_by_stream is false; emit_anthropic_usage_event now writes ttft_ms into the UsageEvent and records a corresponding record_time_to_first_token metric.
Anthropic upstream and non-streaming paths
crates/aisix-proxy/src/messages.rs
Anthropic upstream and cross-provider non-streaming paths set usage_handled_by_stream: false to keep usage emission in wrapper; non-streaming metrics initialized with ttft_ms: 0.
Cross-provider streaming TTFT test coverage
crates/aisix-proxy/src/messages.rs
New test validates that non-Anthropic streaming emits usage event with ttft_ms > 0 and that event is emitted exactly once.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: recording telemetry for messages stream handling when protocol conversion occurs.
Linked Issues check✅ PassedThe PR addresses all coding requirements from issue #359: recording TTFT for Anthropic→OpenAI streaming, using correct inbound_protocol, and capturing token/usage data.
Out of Scope Changes check✅ PassedAll changes in messages.rs are directly related to implementing telemetry recording for protocol-converted streaming requests, staying within the scope of issue #359.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3cc5057b-a0df-4ad1-8003-50e10ee4a07f

📥 Commits

Reviewing files that changed from the base of the PR and between 8d68d31 and e9525c9.

📒 Files selected for processing (1)
  • crates/aisix-proxy/src/messages.rs

Comment threadcrates/aisix-proxy/src/messages.rs

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes telemetry gaps for Anthropic /v1/messagesstreaming requests that are protocol-converted and routed to an OpenAI-compatible upstream. It defers usage/telemetry emission until the upstream stream completes so the proxy can record TTFT, token counts, provider request metadata, and correctly tag inbound_protocol = "anthropic" for the conversion path (while keeping Anthropic-upstream byte-stream passthrough unchanged).

Changes:

  • Plumbs request start time and identity metadata into the cross-provider streaming path and emits usage on stream completion.
  • Adds TTFT tracking for /v1/messages protocol-conversion streaming by observing the first upstream chunk that carries generated output.
  • Adds an E2E-style unit test verifying that cross-provider streaming emits a single usage event with TTFT and upstream metadata.
Comments suppressed due to low confidence (1)

crates/aisix-proxy/src/messages.rs:785

  • finish_reason is derived via format!("{fr:?}").to_lowercase(), which collapses variants like ContentFilter/ToolCalls into contentfilter/toolcalls (missing underscores) and can diverge from the canonical wire labels used elsewhere (e.g., chat.rs’ finish_reason_label). This will produce inconsistent UsageEvent.finish_reason values for /v1/messages protocol-conversion streams. Consider reusing the same mapping (stop, length, content_filter, tool_calls, other) here.
 if let Some(fr) = chunk.finish_reason.as_ref() {
comp.finish_reason = format!("{fr:?}").to_lowercase();
}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadcrates/aisix-proxy/src/messages.rs
CopilotAI review requested due to automatic review settings May 21, 2026 09:16

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@nic-6443
nic-6443 merged commit 807e1df into mainMay 21, 2026
11 of 12 checks passed
@nic-6443
nic-6443 deleted the fix/issue-359-messages-stream-telemetry branch May 21, 2026 09:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Missing log fields in case of protocol conversion

3 participants

@jarvis9443@nic-6443
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(proxy): record messages stream telemetry - #363

Merged
nic-6443 merged 3 commits into
mainfrom
fix/issue-359-messages-stream-telemetry
May 21, 2026
Merged

fix(proxy): record messages stream telemetry#363
nic-6443 merged 3 commits into
mainfrom
fix/issue-359-messages-stream-telemetry

Conversation

@jarvis9443

@jarvis9443jarvis9443 commented May 21, 2026

Copy link
Copy Markdown
Contributor

Fixes#359.

When an Anthropic /v1/messages streaming request is routed to an OpenAI-compatible upstream, telemetry now waits for the stream to finish before emitting usage. That lets the proxy record TTFT, token counts, provider request metadata, canonical finish-reason labels, and inbound_protocol = "anthropic" for the protocol-conversion path.

TTFT histogram samples are only recorded when the first-token measurement is known, so non-streaming/default paths do not add synthetic 0ms samples.

This keeps Anthropic-upstream byte-stream passthrough behavior unchanged.

CopilotAI review requested due to automatic review settings May 21, 2026 09:09
@coderabbitai

coderabbitaiBot commented May 21, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR adds time-to-first-token (TTFT) measurement for cross-provider streaming by threading a started timestamp through the dispatch chain. Cross-provider streaming now computes TTFT when the first content chunk arrives and emits the Anthropic usage event from the stream completion callback instead of the outer wrapper, controlled by a new usage_handled_by_stream flag on DispatchOutcome.

Changes

Cross-provider Streaming TTFT and Conditional Usage Event Emission

Layer / File(s)Summary
Data structures for TTFT and conditional emission
crates/aisix-proxy/src/messages.rs
DispatchOutcome gains usage_handled_by_stream boolean field; AnthropicUsageMetrics gains ttft_ms: u32 field; new AnthropicStreamCompletion data type and CompleteAnthropicStreamOnDrop drop-guard to ensure callback invocation.
Request entry point and timestamp threading
crates/aisix-proxy/src/messages.rs
Handler passes started: Instant to dispatch() and destructures usage_handled_by_stream from DispatchOutcome; dispatch function signature extended to accept started parameter.
Cross-provider streaming with TTFT computation and completion callback
crates/aisix-proxy/src/messages.rs
cross_provider_dispatch signature accepts started and identity fields; streaming path calls build_anthropic_sse_stream with on_complete callback that constructs AnthropicUsageMetrics with computed ttft_ms and emits usage event on stream completion; streaming outcome marks usage_handled_by_stream: true.
Conditional usage event emission and TTFT metric recording
crates/aisix-proxy/src/messages.rs
Wrapper's usage event emission is gated to run only when usage_handled_by_stream is false; emit_anthropic_usage_event now writes ttft_ms into the UsageEvent and records a corresponding record_time_to_first_token metric.
Anthropic upstream and non-streaming paths
crates/aisix-proxy/src/messages.rs
Anthropic upstream and cross-provider non-streaming paths set usage_handled_by_stream: false to keep usage emission in wrapper; non-streaming metrics initialized with ttft_ms: 0.
Cross-provider streaming TTFT test coverage
crates/aisix-proxy/src/messages.rs
New test validates that non-Anthropic streaming emits usage event with ttft_ms > 0 and that event is emitted exactly once.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: recording telemetry for messages stream handling when protocol conversion occurs.
Linked Issues check✅ PassedThe PR addresses all coding requirements from issue #359: recording TTFT for Anthropic→OpenAI streaming, using correct inbound_protocol, and capturing token/usage data.
Out of Scope Changes check✅ PassedAll changes in messages.rs are directly related to implementing telemetry recording for protocol-converted streaming requests, staying within the scope of issue #359.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3cc5057b-a0df-4ad1-8003-50e10ee4a07f

📥 Commits

Reviewing files that changed from the base of the PR and between 8d68d31 and e9525c9.

📒 Files selected for processing (1)
  • crates/aisix-proxy/src/messages.rs

Comment threadcrates/aisix-proxy/src/messages.rs

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes telemetry gaps for Anthropic /v1/messagesstreaming requests that are protocol-converted and routed to an OpenAI-compatible upstream. It defers usage/telemetry emission until the upstream stream completes so the proxy can record TTFT, token counts, provider request metadata, and correctly tag inbound_protocol = "anthropic" for the conversion path (while keeping Anthropic-upstream byte-stream passthrough unchanged).

Changes:

  • Plumbs request start time and identity metadata into the cross-provider streaming path and emits usage on stream completion.
  • Adds TTFT tracking for /v1/messages protocol-conversion streaming by observing the first upstream chunk that carries generated output.
  • Adds an E2E-style unit test verifying that cross-provider streaming emits a single usage event with TTFT and upstream metadata.
Comments suppressed due to low confidence (1)

crates/aisix-proxy/src/messages.rs:785

  • finish_reason is derived via format!("{fr:?}").to_lowercase(), which collapses variants like ContentFilter/ToolCalls into contentfilter/toolcalls (missing underscores) and can diverge from the canonical wire labels used elsewhere (e.g., chat.rs’ finish_reason_label). This will produce inconsistent UsageEvent.finish_reason values for /v1/messages protocol-conversion streams. Consider reusing the same mapping (stop, length, content_filter, tool_calls, other) here.
 if let Some(fr) = chunk.finish_reason.as_ref() {
comp.finish_reason = format!("{fr:?}").to_lowercase();
}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadcrates/aisix-proxy/src/messages.rs
CopilotAI review requested due to automatic review settings May 21, 2026 09:16

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@nic-6443
nic-6443 merged commit 807e1df into mainMay 21, 2026
11 of 12 checks passed
@nic-6443
nic-6443 deleted the fix/issue-359-messages-stream-telemetry branch May 21, 2026 09:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Missing log fields in case of protocol conversion

3 participants

@jarvis9443@nic-6443
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(proxy): record messages stream telemetry - #363

Merged
nic-6443 merged 3 commits into
mainfrom
fix/issue-359-messages-stream-telemetry
May 21, 2026
Merged

fix(proxy): record messages stream telemetry#363
nic-6443 merged 3 commits into
mainfrom
fix/issue-359-messages-stream-telemetry

Conversation

@jarvis9443

@jarvis9443jarvis9443 commented May 21, 2026

Copy link
Copy Markdown
Contributor

Fixes#359.

When an Anthropic /v1/messages streaming request is routed to an OpenAI-compatible upstream, telemetry now waits for the stream to finish before emitting usage. That lets the proxy record TTFT, token counts, provider request metadata, canonical finish-reason labels, and inbound_protocol = "anthropic" for the protocol-conversion path.

TTFT histogram samples are only recorded when the first-token measurement is known, so non-streaming/default paths do not add synthetic 0ms samples.

This keeps Anthropic-upstream byte-stream passthrough behavior unchanged.

CopilotAI review requested due to automatic review settings May 21, 2026 09:09
@coderabbitai

coderabbitaiBot commented May 21, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR adds time-to-first-token (TTFT) measurement for cross-provider streaming by threading a started timestamp through the dispatch chain. Cross-provider streaming now computes TTFT when the first content chunk arrives and emits the Anthropic usage event from the stream completion callback instead of the outer wrapper, controlled by a new usage_handled_by_stream flag on DispatchOutcome.

Changes

Cross-provider Streaming TTFT and Conditional Usage Event Emission

Layer / File(s)Summary
Data structures for TTFT and conditional emission
crates/aisix-proxy/src/messages.rs
DispatchOutcome gains usage_handled_by_stream boolean field; AnthropicUsageMetrics gains ttft_ms: u32 field; new AnthropicStreamCompletion data type and CompleteAnthropicStreamOnDrop drop-guard to ensure callback invocation.
Request entry point and timestamp threading
crates/aisix-proxy/src/messages.rs
Handler passes started: Instant to dispatch() and destructures usage_handled_by_stream from DispatchOutcome; dispatch function signature extended to accept started parameter.
Cross-provider streaming with TTFT computation and completion callback
crates/aisix-proxy/src/messages.rs
cross_provider_dispatch signature accepts started and identity fields; streaming path calls build_anthropic_sse_stream with on_complete callback that constructs AnthropicUsageMetrics with computed ttft_ms and emits usage event on stream completion; streaming outcome marks usage_handled_by_stream: true.
Conditional usage event emission and TTFT metric recording
crates/aisix-proxy/src/messages.rs
Wrapper's usage event emission is gated to run only when usage_handled_by_stream is false; emit_anthropic_usage_event now writes ttft_ms into the UsageEvent and records a corresponding record_time_to_first_token metric.
Anthropic upstream and non-streaming paths
crates/aisix-proxy/src/messages.rs
Anthropic upstream and cross-provider non-streaming paths set usage_handled_by_stream: false to keep usage emission in wrapper; non-streaming metrics initialized with ttft_ms: 0.
Cross-provider streaming TTFT test coverage
crates/aisix-proxy/src/messages.rs
New test validates that non-Anthropic streaming emits usage event with ttft_ms > 0 and that event is emitted exactly once.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: recording telemetry for messages stream handling when protocol conversion occurs.
Linked Issues check✅ PassedThe PR addresses all coding requirements from issue #359: recording TTFT for Anthropic→OpenAI streaming, using correct inbound_protocol, and capturing token/usage data.
Out of Scope Changes check✅ PassedAll changes in messages.rs are directly related to implementing telemetry recording for protocol-converted streaming requests, staying within the scope of issue #359.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3cc5057b-a0df-4ad1-8003-50e10ee4a07f

📥 Commits

Reviewing files that changed from the base of the PR and between 8d68d31 and e9525c9.

📒 Files selected for processing (1)
  • crates/aisix-proxy/src/messages.rs

Comment threadcrates/aisix-proxy/src/messages.rs

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes telemetry gaps for Anthropic /v1/messagesstreaming requests that are protocol-converted and routed to an OpenAI-compatible upstream. It defers usage/telemetry emission until the upstream stream completes so the proxy can record TTFT, token counts, provider request metadata, and correctly tag inbound_protocol = "anthropic" for the conversion path (while keeping Anthropic-upstream byte-stream passthrough unchanged).

Changes:

  • Plumbs request start time and identity metadata into the cross-provider streaming path and emits usage on stream completion.
  • Adds TTFT tracking for /v1/messages protocol-conversion streaming by observing the first upstream chunk that carries generated output.
  • Adds an E2E-style unit test verifying that cross-provider streaming emits a single usage event with TTFT and upstream metadata.
Comments suppressed due to low confidence (1)

crates/aisix-proxy/src/messages.rs:785

  • finish_reason is derived via format!("{fr:?}").to_lowercase(), which collapses variants like ContentFilter/ToolCalls into contentfilter/toolcalls (missing underscores) and can diverge from the canonical wire labels used elsewhere (e.g., chat.rs’ finish_reason_label). This will produce inconsistent UsageEvent.finish_reason values for /v1/messages protocol-conversion streams. Consider reusing the same mapping (stop, length, content_filter, tool_calls, other) here.
 if let Some(fr) = chunk.finish_reason.as_ref() {
comp.finish_reason = format!("{fr:?}").to_lowercase();
}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadcrates/aisix-proxy/src/messages.rs
CopilotAI review requested due to automatic review settings May 21, 2026 09:16

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@nic-6443
nic-6443 merged commit 807e1df into mainMay 21, 2026
11 of 12 checks passed
@nic-6443
nic-6443 deleted the fix/issue-359-messages-stream-telemetry branch May 21, 2026 09:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Missing log fields in case of protocol conversion

3 participants

@jarvis9443@nic-6443
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(proxy): record messages stream telemetry - #363

Merged
nic-6443 merged 3 commits into
mainfrom
fix/issue-359-messages-stream-telemetry
May 21, 2026
Merged

fix(proxy): record messages stream telemetry#363
nic-6443 merged 3 commits into
mainfrom
fix/issue-359-messages-stream-telemetry

Conversation

@jarvis9443

@jarvis9443jarvis9443 commented May 21, 2026

Copy link
Copy Markdown
Contributor

Fixes#359.

When an Anthropic /v1/messages streaming request is routed to an OpenAI-compatible upstream, telemetry now waits for the stream to finish before emitting usage. That lets the proxy record TTFT, token counts, provider request metadata, canonical finish-reason labels, and inbound_protocol = "anthropic" for the protocol-conversion path.

TTFT histogram samples are only recorded when the first-token measurement is known, so non-streaming/default paths do not add synthetic 0ms samples.

This keeps Anthropic-upstream byte-stream passthrough behavior unchanged.

CopilotAI review requested due to automatic review settings May 21, 2026 09:09
@coderabbitai

coderabbitaiBot commented May 21, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR adds time-to-first-token (TTFT) measurement for cross-provider streaming by threading a started timestamp through the dispatch chain. Cross-provider streaming now computes TTFT when the first content chunk arrives and emits the Anthropic usage event from the stream completion callback instead of the outer wrapper, controlled by a new usage_handled_by_stream flag on DispatchOutcome.

Changes

Cross-provider Streaming TTFT and Conditional Usage Event Emission

Layer / File(s)Summary
Data structures for TTFT and conditional emission
crates/aisix-proxy/src/messages.rs
DispatchOutcome gains usage_handled_by_stream boolean field; AnthropicUsageMetrics gains ttft_ms: u32 field; new AnthropicStreamCompletion data type and CompleteAnthropicStreamOnDrop drop-guard to ensure callback invocation.
Request entry point and timestamp threading
crates/aisix-proxy/src/messages.rs
Handler passes started: Instant to dispatch() and destructures usage_handled_by_stream from DispatchOutcome; dispatch function signature extended to accept started parameter.
Cross-provider streaming with TTFT computation and completion callback
crates/aisix-proxy/src/messages.rs
cross_provider_dispatch signature accepts started and identity fields; streaming path calls build_anthropic_sse_stream with on_complete callback that constructs AnthropicUsageMetrics with computed ttft_ms and emits usage event on stream completion; streaming outcome marks usage_handled_by_stream: true.
Conditional usage event emission and TTFT metric recording
crates/aisix-proxy/src/messages.rs
Wrapper's usage event emission is gated to run only when usage_handled_by_stream is false; emit_anthropic_usage_event now writes ttft_ms into the UsageEvent and records a corresponding record_time_to_first_token metric.
Anthropic upstream and non-streaming paths
crates/aisix-proxy/src/messages.rs
Anthropic upstream and cross-provider non-streaming paths set usage_handled_by_stream: false to keep usage emission in wrapper; non-streaming metrics initialized with ttft_ms: 0.
Cross-provider streaming TTFT test coverage
crates/aisix-proxy/src/messages.rs
New test validates that non-Anthropic streaming emits usage event with ttft_ms > 0 and that event is emitted exactly once.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: recording telemetry for messages stream handling when protocol conversion occurs.
Linked Issues check✅ PassedThe PR addresses all coding requirements from issue #359: recording TTFT for Anthropic→OpenAI streaming, using correct inbound_protocol, and capturing token/usage data.
Out of Scope Changes check✅ PassedAll changes in messages.rs are directly related to implementing telemetry recording for protocol-converted streaming requests, staying within the scope of issue #359.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3cc5057b-a0df-4ad1-8003-50e10ee4a07f

📥 Commits

Reviewing files that changed from the base of the PR and between 8d68d31 and e9525c9.

📒 Files selected for processing (1)
  • crates/aisix-proxy/src/messages.rs

Comment threadcrates/aisix-proxy/src/messages.rs

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes telemetry gaps for Anthropic /v1/messagesstreaming requests that are protocol-converted and routed to an OpenAI-compatible upstream. It defers usage/telemetry emission until the upstream stream completes so the proxy can record TTFT, token counts, provider request metadata, and correctly tag inbound_protocol = "anthropic" for the conversion path (while keeping Anthropic-upstream byte-stream passthrough unchanged).

Changes:

  • Plumbs request start time and identity metadata into the cross-provider streaming path and emits usage on stream completion.
  • Adds TTFT tracking for /v1/messages protocol-conversion streaming by observing the first upstream chunk that carries generated output.
  • Adds an E2E-style unit test verifying that cross-provider streaming emits a single usage event with TTFT and upstream metadata.
Comments suppressed due to low confidence (1)

crates/aisix-proxy/src/messages.rs:785

  • finish_reason is derived via format!("{fr:?}").to_lowercase(), which collapses variants like ContentFilter/ToolCalls into contentfilter/toolcalls (missing underscores) and can diverge from the canonical wire labels used elsewhere (e.g., chat.rs’ finish_reason_label). This will produce inconsistent UsageEvent.finish_reason values for /v1/messages protocol-conversion streams. Consider reusing the same mapping (stop, length, content_filter, tool_calls, other) here.
 if let Some(fr) = chunk.finish_reason.as_ref() {
comp.finish_reason = format!("{fr:?}").to_lowercase();
}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadcrates/aisix-proxy/src/messages.rs
CopilotAI review requested due to automatic review settings May 21, 2026 09:16

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@nic-6443
nic-6443 merged commit 807e1df into mainMay 21, 2026
11 of 12 checks passed
@nic-6443
nic-6443 deleted the fix/issue-359-messages-stream-telemetry branch May 21, 2026 09:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Missing log fields in case of protocol conversion

3 participants

@jarvis9443@nic-6443
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(proxy): record messages stream telemetry - #363

Merged
nic-6443 merged 3 commits into
mainfrom
fix/issue-359-messages-stream-telemetry
May 21, 2026
Merged

fix(proxy): record messages stream telemetry#363
nic-6443 merged 3 commits into
mainfrom
fix/issue-359-messages-stream-telemetry

Conversation

@jarvis9443

@jarvis9443jarvis9443 commented May 21, 2026

Copy link
Copy Markdown
Contributor

Fixes#359.

When an Anthropic /v1/messages streaming request is routed to an OpenAI-compatible upstream, telemetry now waits for the stream to finish before emitting usage. That lets the proxy record TTFT, token counts, provider request metadata, canonical finish-reason labels, and inbound_protocol = "anthropic" for the protocol-conversion path.

TTFT histogram samples are only recorded when the first-token measurement is known, so non-streaming/default paths do not add synthetic 0ms samples.

This keeps Anthropic-upstream byte-stream passthrough behavior unchanged.

CopilotAI review requested due to automatic review settings May 21, 2026 09:09
@coderabbitai

coderabbitaiBot commented May 21, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR adds time-to-first-token (TTFT) measurement for cross-provider streaming by threading a started timestamp through the dispatch chain. Cross-provider streaming now computes TTFT when the first content chunk arrives and emits the Anthropic usage event from the stream completion callback instead of the outer wrapper, controlled by a new usage_handled_by_stream flag on DispatchOutcome.

Changes

Cross-provider Streaming TTFT and Conditional Usage Event Emission

Layer / File(s)Summary
Data structures for TTFT and conditional emission
crates/aisix-proxy/src/messages.rs
DispatchOutcome gains usage_handled_by_stream boolean field; AnthropicUsageMetrics gains ttft_ms: u32 field; new AnthropicStreamCompletion data type and CompleteAnthropicStreamOnDrop drop-guard to ensure callback invocation.
Request entry point and timestamp threading
crates/aisix-proxy/src/messages.rs
Handler passes started: Instant to dispatch() and destructures usage_handled_by_stream from DispatchOutcome; dispatch function signature extended to accept started parameter.
Cross-provider streaming with TTFT computation and completion callback
crates/aisix-proxy/src/messages.rs
cross_provider_dispatch signature accepts started and identity fields; streaming path calls build_anthropic_sse_stream with on_complete callback that constructs AnthropicUsageMetrics with computed ttft_ms and emits usage event on stream completion; streaming outcome marks usage_handled_by_stream: true.
Conditional usage event emission and TTFT metric recording
crates/aisix-proxy/src/messages.rs
Wrapper's usage event emission is gated to run only when usage_handled_by_stream is false; emit_anthropic_usage_event now writes ttft_ms into the UsageEvent and records a corresponding record_time_to_first_token metric.
Anthropic upstream and non-streaming paths
crates/aisix-proxy/src/messages.rs
Anthropic upstream and cross-provider non-streaming paths set usage_handled_by_stream: false to keep usage emission in wrapper; non-streaming metrics initialized with ttft_ms: 0.
Cross-provider streaming TTFT test coverage
crates/aisix-proxy/src/messages.rs
New test validates that non-Anthropic streaming emits usage event with ttft_ms > 0 and that event is emitted exactly once.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: recording telemetry for messages stream handling when protocol conversion occurs.
Linked Issues check✅ PassedThe PR addresses all coding requirements from issue #359: recording TTFT for Anthropic→OpenAI streaming, using correct inbound_protocol, and capturing token/usage data.
Out of Scope Changes check✅ PassedAll changes in messages.rs are directly related to implementing telemetry recording for protocol-converted streaming requests, staying within the scope of issue #359.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3cc5057b-a0df-4ad1-8003-50e10ee4a07f

📥 Commits

Reviewing files that changed from the base of the PR and between 8d68d31 and e9525c9.

📒 Files selected for processing (1)
  • crates/aisix-proxy/src/messages.rs

Comment threadcrates/aisix-proxy/src/messages.rs

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes telemetry gaps for Anthropic /v1/messagesstreaming requests that are protocol-converted and routed to an OpenAI-compatible upstream. It defers usage/telemetry emission until the upstream stream completes so the proxy can record TTFT, token counts, provider request metadata, and correctly tag inbound_protocol = "anthropic" for the conversion path (while keeping Anthropic-upstream byte-stream passthrough unchanged).

Changes:

  • Plumbs request start time and identity metadata into the cross-provider streaming path and emits usage on stream completion.
  • Adds TTFT tracking for /v1/messages protocol-conversion streaming by observing the first upstream chunk that carries generated output.
  • Adds an E2E-style unit test verifying that cross-provider streaming emits a single usage event with TTFT and upstream metadata.
Comments suppressed due to low confidence (1)

crates/aisix-proxy/src/messages.rs:785

  • finish_reason is derived via format!("{fr:?}").to_lowercase(), which collapses variants like ContentFilter/ToolCalls into contentfilter/toolcalls (missing underscores) and can diverge from the canonical wire labels used elsewhere (e.g., chat.rs’ finish_reason_label). This will produce inconsistent UsageEvent.finish_reason values for /v1/messages protocol-conversion streams. Consider reusing the same mapping (stop, length, content_filter, tool_calls, other) here.
 if let Some(fr) = chunk.finish_reason.as_ref() {
comp.finish_reason = format!("{fr:?}").to_lowercase();
}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadcrates/aisix-proxy/src/messages.rs
CopilotAI review requested due to automatic review settings May 21, 2026 09:16

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@nic-6443
nic-6443 merged commit 807e1df into mainMay 21, 2026
11 of 12 checks passed
@nic-6443
nic-6443 deleted the fix/issue-359-messages-stream-telemetry branch May 21, 2026 09:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Missing log fields in case of protocol conversion

3 participants

@jarvis9443@nic-6443
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(proxy): record messages stream telemetry - #363

Merged
nic-6443 merged 3 commits into
mainfrom
fix/issue-359-messages-stream-telemetry
May 21, 2026
Merged

fix(proxy): record messages stream telemetry#363
nic-6443 merged 3 commits into
mainfrom
fix/issue-359-messages-stream-telemetry

Conversation

@jarvis9443

@jarvis9443jarvis9443 commented May 21, 2026

Copy link
Copy Markdown
Contributor

Fixes#359.

When an Anthropic /v1/messages streaming request is routed to an OpenAI-compatible upstream, telemetry now waits for the stream to finish before emitting usage. That lets the proxy record TTFT, token counts, provider request metadata, canonical finish-reason labels, and inbound_protocol = "anthropic" for the protocol-conversion path.

TTFT histogram samples are only recorded when the first-token measurement is known, so non-streaming/default paths do not add synthetic 0ms samples.

This keeps Anthropic-upstream byte-stream passthrough behavior unchanged.

CopilotAI review requested due to automatic review settings May 21, 2026 09:09
@coderabbitai

coderabbitaiBot commented May 21, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR adds time-to-first-token (TTFT) measurement for cross-provider streaming by threading a started timestamp through the dispatch chain. Cross-provider streaming now computes TTFT when the first content chunk arrives and emits the Anthropic usage event from the stream completion callback instead of the outer wrapper, controlled by a new usage_handled_by_stream flag on DispatchOutcome.

Changes

Cross-provider Streaming TTFT and Conditional Usage Event Emission

Layer / File(s)Summary
Data structures for TTFT and conditional emission
crates/aisix-proxy/src/messages.rs
DispatchOutcome gains usage_handled_by_stream boolean field; AnthropicUsageMetrics gains ttft_ms: u32 field; new AnthropicStreamCompletion data type and CompleteAnthropicStreamOnDrop drop-guard to ensure callback invocation.
Request entry point and timestamp threading
crates/aisix-proxy/src/messages.rs
Handler passes started: Instant to dispatch() and destructures usage_handled_by_stream from DispatchOutcome; dispatch function signature extended to accept started parameter.
Cross-provider streaming with TTFT computation and completion callback
crates/aisix-proxy/src/messages.rs
cross_provider_dispatch signature accepts started and identity fields; streaming path calls build_anthropic_sse_stream with on_complete callback that constructs AnthropicUsageMetrics with computed ttft_ms and emits usage event on stream completion; streaming outcome marks usage_handled_by_stream: true.
Conditional usage event emission and TTFT metric recording
crates/aisix-proxy/src/messages.rs
Wrapper's usage event emission is gated to run only when usage_handled_by_stream is false; emit_anthropic_usage_event now writes ttft_ms into the UsageEvent and records a corresponding record_time_to_first_token metric.
Anthropic upstream and non-streaming paths
crates/aisix-proxy/src/messages.rs
Anthropic upstream and cross-provider non-streaming paths set usage_handled_by_stream: false to keep usage emission in wrapper; non-streaming metrics initialized with ttft_ms: 0.
Cross-provider streaming TTFT test coverage
crates/aisix-proxy/src/messages.rs
New test validates that non-Anthropic streaming emits usage event with ttft_ms > 0 and that event is emitted exactly once.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: recording telemetry for messages stream handling when protocol conversion occurs.
Linked Issues check✅ PassedThe PR addresses all coding requirements from issue #359: recording TTFT for Anthropic→OpenAI streaming, using correct inbound_protocol, and capturing token/usage data.
Out of Scope Changes check✅ PassedAll changes in messages.rs are directly related to implementing telemetry recording for protocol-converted streaming requests, staying within the scope of issue #359.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3cc5057b-a0df-4ad1-8003-50e10ee4a07f

📥 Commits

Reviewing files that changed from the base of the PR and between 8d68d31 and e9525c9.

📒 Files selected for processing (1)
  • crates/aisix-proxy/src/messages.rs

Comment threadcrates/aisix-proxy/src/messages.rs

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes telemetry gaps for Anthropic /v1/messagesstreaming requests that are protocol-converted and routed to an OpenAI-compatible upstream. It defers usage/telemetry emission until the upstream stream completes so the proxy can record TTFT, token counts, provider request metadata, and correctly tag inbound_protocol = "anthropic" for the conversion path (while keeping Anthropic-upstream byte-stream passthrough unchanged).

Changes:

  • Plumbs request start time and identity metadata into the cross-provider streaming path and emits usage on stream completion.
  • Adds TTFT tracking for /v1/messages protocol-conversion streaming by observing the first upstream chunk that carries generated output.
  • Adds an E2E-style unit test verifying that cross-provider streaming emits a single usage event with TTFT and upstream metadata.
Comments suppressed due to low confidence (1)

crates/aisix-proxy/src/messages.rs:785

  • finish_reason is derived via format!("{fr:?}").to_lowercase(), which collapses variants like ContentFilter/ToolCalls into contentfilter/toolcalls (missing underscores) and can diverge from the canonical wire labels used elsewhere (e.g., chat.rs’ finish_reason_label). This will produce inconsistent UsageEvent.finish_reason values for /v1/messages protocol-conversion streams. Consider reusing the same mapping (stop, length, content_filter, tool_calls, other) here.
 if let Some(fr) = chunk.finish_reason.as_ref() {
comp.finish_reason = format!("{fr:?}").to_lowercase();
}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadcrates/aisix-proxy/src/messages.rs
CopilotAI review requested due to automatic review settings May 21, 2026 09:16

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@nic-6443
nic-6443 merged commit 807e1df into mainMay 21, 2026
11 of 12 checks passed
@nic-6443
nic-6443 deleted the fix/issue-359-messages-stream-telemetry branch May 21, 2026 09:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Missing log fields in case of protocol conversion

3 participants

@jarvis9443@nic-6443
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(proxy): record messages stream telemetry - #363

Merged
nic-6443 merged 3 commits into
mainfrom
fix/issue-359-messages-stream-telemetry
May 21, 2026
Merged

fix(proxy): record messages stream telemetry#363
nic-6443 merged 3 commits into
mainfrom
fix/issue-359-messages-stream-telemetry

Conversation

@jarvis9443

@jarvis9443jarvis9443 commented May 21, 2026

Copy link
Copy Markdown
Contributor

Fixes#359.

When an Anthropic /v1/messages streaming request is routed to an OpenAI-compatible upstream, telemetry now waits for the stream to finish before emitting usage. That lets the proxy record TTFT, token counts, provider request metadata, canonical finish-reason labels, and inbound_protocol = "anthropic" for the protocol-conversion path.

TTFT histogram samples are only recorded when the first-token measurement is known, so non-streaming/default paths do not add synthetic 0ms samples.

This keeps Anthropic-upstream byte-stream passthrough behavior unchanged.

CopilotAI review requested due to automatic review settings May 21, 2026 09:09
@coderabbitai

coderabbitaiBot commented May 21, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR adds time-to-first-token (TTFT) measurement for cross-provider streaming by threading a started timestamp through the dispatch chain. Cross-provider streaming now computes TTFT when the first content chunk arrives and emits the Anthropic usage event from the stream completion callback instead of the outer wrapper, controlled by a new usage_handled_by_stream flag on DispatchOutcome.

Changes

Cross-provider Streaming TTFT and Conditional Usage Event Emission

Layer / File(s)Summary
Data structures for TTFT and conditional emission
crates/aisix-proxy/src/messages.rs
DispatchOutcome gains usage_handled_by_stream boolean field; AnthropicUsageMetrics gains ttft_ms: u32 field; new AnthropicStreamCompletion data type and CompleteAnthropicStreamOnDrop drop-guard to ensure callback invocation.
Request entry point and timestamp threading
crates/aisix-proxy/src/messages.rs
Handler passes started: Instant to dispatch() and destructures usage_handled_by_stream from DispatchOutcome; dispatch function signature extended to accept started parameter.
Cross-provider streaming with TTFT computation and completion callback
crates/aisix-proxy/src/messages.rs
cross_provider_dispatch signature accepts started and identity fields; streaming path calls build_anthropic_sse_stream with on_complete callback that constructs AnthropicUsageMetrics with computed ttft_ms and emits usage event on stream completion; streaming outcome marks usage_handled_by_stream: true.
Conditional usage event emission and TTFT metric recording
crates/aisix-proxy/src/messages.rs
Wrapper's usage event emission is gated to run only when usage_handled_by_stream is false; emit_anthropic_usage_event now writes ttft_ms into the UsageEvent and records a corresponding record_time_to_first_token metric.
Anthropic upstream and non-streaming paths
crates/aisix-proxy/src/messages.rs
Anthropic upstream and cross-provider non-streaming paths set usage_handled_by_stream: false to keep usage emission in wrapper; non-streaming metrics initialized with ttft_ms: 0.
Cross-provider streaming TTFT test coverage
crates/aisix-proxy/src/messages.rs
New test validates that non-Anthropic streaming emits usage event with ttft_ms > 0 and that event is emitted exactly once.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: recording telemetry for messages stream handling when protocol conversion occurs.
Linked Issues check✅ PassedThe PR addresses all coding requirements from issue #359: recording TTFT for Anthropic→OpenAI streaming, using correct inbound_protocol, and capturing token/usage data.
Out of Scope Changes check✅ PassedAll changes in messages.rs are directly related to implementing telemetry recording for protocol-converted streaming requests, staying within the scope of issue #359.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3cc5057b-a0df-4ad1-8003-50e10ee4a07f

📥 Commits

Reviewing files that changed from the base of the PR and between 8d68d31 and e9525c9.

📒 Files selected for processing (1)
  • crates/aisix-proxy/src/messages.rs

Comment threadcrates/aisix-proxy/src/messages.rs

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes telemetry gaps for Anthropic /v1/messagesstreaming requests that are protocol-converted and routed to an OpenAI-compatible upstream. It defers usage/telemetry emission until the upstream stream completes so the proxy can record TTFT, token counts, provider request metadata, and correctly tag inbound_protocol = "anthropic" for the conversion path (while keeping Anthropic-upstream byte-stream passthrough unchanged).

Changes:

  • Plumbs request start time and identity metadata into the cross-provider streaming path and emits usage on stream completion.
  • Adds TTFT tracking for /v1/messages protocol-conversion streaming by observing the first upstream chunk that carries generated output.
  • Adds an E2E-style unit test verifying that cross-provider streaming emits a single usage event with TTFT and upstream metadata.
Comments suppressed due to low confidence (1)

crates/aisix-proxy/src/messages.rs:785

  • finish_reason is derived via format!("{fr:?}").to_lowercase(), which collapses variants like ContentFilter/ToolCalls into contentfilter/toolcalls (missing underscores) and can diverge from the canonical wire labels used elsewhere (e.g., chat.rs’ finish_reason_label). This will produce inconsistent UsageEvent.finish_reason values for /v1/messages protocol-conversion streams. Consider reusing the same mapping (stop, length, content_filter, tool_calls, other) here.
 if let Some(fr) = chunk.finish_reason.as_ref() {
comp.finish_reason = format!("{fr:?}").to_lowercase();
}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadcrates/aisix-proxy/src/messages.rs
CopilotAI review requested due to automatic review settings May 21, 2026 09:16

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@nic-6443
nic-6443 merged commit 807e1df into mainMay 21, 2026
11 of 12 checks passed
@nic-6443
nic-6443 deleted the fix/issue-359-messages-stream-telemetry branch May 21, 2026 09:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Missing log fields in case of protocol conversion

3 participants

@jarvis9443@nic-6443
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(proxy): record messages stream telemetry - #363

Merged
nic-6443 merged 3 commits into
mainfrom
fix/issue-359-messages-stream-telemetry
May 21, 2026
Merged

fix(proxy): record messages stream telemetry#363
nic-6443 merged 3 commits into
mainfrom
fix/issue-359-messages-stream-telemetry

Conversation

@jarvis9443

@jarvis9443jarvis9443 commented May 21, 2026

Copy link
Copy Markdown
Contributor

Fixes#359.

When an Anthropic /v1/messages streaming request is routed to an OpenAI-compatible upstream, telemetry now waits for the stream to finish before emitting usage. That lets the proxy record TTFT, token counts, provider request metadata, canonical finish-reason labels, and inbound_protocol = "anthropic" for the protocol-conversion path.

TTFT histogram samples are only recorded when the first-token measurement is known, so non-streaming/default paths do not add synthetic 0ms samples.

This keeps Anthropic-upstream byte-stream passthrough behavior unchanged.

CopilotAI review requested due to automatic review settings May 21, 2026 09:09
@coderabbitai

coderabbitaiBot commented May 21, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR adds time-to-first-token (TTFT) measurement for cross-provider streaming by threading a started timestamp through the dispatch chain. Cross-provider streaming now computes TTFT when the first content chunk arrives and emits the Anthropic usage event from the stream completion callback instead of the outer wrapper, controlled by a new usage_handled_by_stream flag on DispatchOutcome.

Changes

Cross-provider Streaming TTFT and Conditional Usage Event Emission

Layer / File(s)Summary
Data structures for TTFT and conditional emission
crates/aisix-proxy/src/messages.rs
DispatchOutcome gains usage_handled_by_stream boolean field; AnthropicUsageMetrics gains ttft_ms: u32 field; new AnthropicStreamCompletion data type and CompleteAnthropicStreamOnDrop drop-guard to ensure callback invocation.
Request entry point and timestamp threading
crates/aisix-proxy/src/messages.rs
Handler passes started: Instant to dispatch() and destructures usage_handled_by_stream from DispatchOutcome; dispatch function signature extended to accept started parameter.
Cross-provider streaming with TTFT computation and completion callback
crates/aisix-proxy/src/messages.rs
cross_provider_dispatch signature accepts started and identity fields; streaming path calls build_anthropic_sse_stream with on_complete callback that constructs AnthropicUsageMetrics with computed ttft_ms and emits usage event on stream completion; streaming outcome marks usage_handled_by_stream: true.
Conditional usage event emission and TTFT metric recording
crates/aisix-proxy/src/messages.rs
Wrapper's usage event emission is gated to run only when usage_handled_by_stream is false; emit_anthropic_usage_event now writes ttft_ms into the UsageEvent and records a corresponding record_time_to_first_token metric.
Anthropic upstream and non-streaming paths
crates/aisix-proxy/src/messages.rs
Anthropic upstream and cross-provider non-streaming paths set usage_handled_by_stream: false to keep usage emission in wrapper; non-streaming metrics initialized with ttft_ms: 0.
Cross-provider streaming TTFT test coverage
crates/aisix-proxy/src/messages.rs
New test validates that non-Anthropic streaming emits usage event with ttft_ms > 0 and that event is emitted exactly once.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: recording telemetry for messages stream handling when protocol conversion occurs.
Linked Issues check✅ PassedThe PR addresses all coding requirements from issue #359: recording TTFT for Anthropic→OpenAI streaming, using correct inbound_protocol, and capturing token/usage data.
Out of Scope Changes check✅ PassedAll changes in messages.rs are directly related to implementing telemetry recording for protocol-converted streaming requests, staying within the scope of issue #359.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3cc5057b-a0df-4ad1-8003-50e10ee4a07f

📥 Commits

Reviewing files that changed from the base of the PR and between 8d68d31 and e9525c9.

📒 Files selected for processing (1)
  • crates/aisix-proxy/src/messages.rs

Comment threadcrates/aisix-proxy/src/messages.rs

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes telemetry gaps for Anthropic /v1/messagesstreaming requests that are protocol-converted and routed to an OpenAI-compatible upstream. It defers usage/telemetry emission until the upstream stream completes so the proxy can record TTFT, token counts, provider request metadata, and correctly tag inbound_protocol = "anthropic" for the conversion path (while keeping Anthropic-upstream byte-stream passthrough unchanged).

Changes:

  • Plumbs request start time and identity metadata into the cross-provider streaming path and emits usage on stream completion.
  • Adds TTFT tracking for /v1/messages protocol-conversion streaming by observing the first upstream chunk that carries generated output.
  • Adds an E2E-style unit test verifying that cross-provider streaming emits a single usage event with TTFT and upstream metadata.
Comments suppressed due to low confidence (1)

crates/aisix-proxy/src/messages.rs:785

  • finish_reason is derived via format!("{fr:?}").to_lowercase(), which collapses variants like ContentFilter/ToolCalls into contentfilter/toolcalls (missing underscores) and can diverge from the canonical wire labels used elsewhere (e.g., chat.rs’ finish_reason_label). This will produce inconsistent UsageEvent.finish_reason values for /v1/messages protocol-conversion streams. Consider reusing the same mapping (stop, length, content_filter, tool_calls, other) here.
 if let Some(fr) = chunk.finish_reason.as_ref() {
comp.finish_reason = format!("{fr:?}").to_lowercase();
}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadcrates/aisix-proxy/src/messages.rs
CopilotAI review requested due to automatic review settings May 21, 2026 09:16

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@nic-6443
nic-6443 merged commit 807e1df into mainMay 21, 2026
11 of 12 checks passed
@nic-6443
nic-6443 deleted the fix/issue-359-messages-stream-telemetry branch May 21, 2026 09:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Missing log fields in case of protocol conversion

3 participants

@jarvis9443@nic-6443