Skip to content

feat: surface the LLM Gateway per-request dollar cost instead of dropping it - #136

Merged
andreitava-uip merged 2 commits into
mainfrom
feat/keep-llmgw-dollar-cost
Sep 9, 2026
Merged

feat: surface the LLM Gateway per-request dollar cost instead of dropping it#136
andreitava-uip merged 2 commits into
mainfrom
feat/keep-llmgw-dollar-cost

Conversation

@andreitava-uip

Copy link
Copy Markdown
Collaborator

What

Stop dropping the per-request dollar cost the LLM Gateway reports for opted-in calls (X-UiPath-LlmGateway-IncludeAssociatedDollarCost: true, Passthrough API only). The gateway adds associated_dollar_cost to non-streaming JSON bodies and appends a trailing frame to streams (SSE data: event, AWS event-stream costMetadata message); the vendor SDKs re-parse the body or stop reading at their terminal event, so callers never saw it.

Core (uipath-llm-client) — new utils/dollar_cost.py. The httpx clients capture the value into a ContextVar for opted-in requests. Non-streaming bodies are read directly. Streaming responses are wrapped: the value is captured when the stream is exhausted, or drained on close when the SDK stops at the vendor's terminal event (openai at [DONE], langchain-aws at Bedrock's stop event). Requested-but-unreadable costs (compressed stream, lost framing, non-numeric value) log a warning.

LangChain (uipath-langchain-client)invoke()/ainvoke() set response_metadata["associated_dollar_cost"]; stream()/astream() yield one extra empty chunk carrying it after the vendor's last chunk (the cost is only known then; holding chunks back would delay every token). The Bedrock shim hides the gateway's costMetadata event from langchain-aws, which raises on unknown events. Absence means "not priced", never $0.

Both packages bump to 1.19.0.

Known residual

Opted-in Gemini streams log langchain-google-genai's "Gemini produced an empty response" warning for the gateway's trailing frame; the cost is still captured.

Tests

Unit tests for the frame parsers and stream wrappers, plus end-to-end tests through the real openai, anthropic and langchain-aws SDKs over lazy per-event mock transports (UiPathChatOpenAI, UiPathChatBedrockConverse, UiPathChatBedrock, UiPathChatAnthropicBedrock), covering invoke, stream, invoke(streaming=True), opted-out and unpriced cases.

…ping it

Callers can opt in with X-UiPath-LlmGateway-IncludeAssociatedDollarCost: true
(Passthrough API). The gateway then adds associated_dollar_cost to non-streaming
JSON bodies and appends a trailing frame to streams (SSE data event, AWS
event-stream costMetadata message). The vendor SDKs re-parse the body or stop
reading at their terminal event, so the value never reached callers.

Core: UiPathHttpxClient/UiPathHttpxAsyncClient capture it into a ContextVar for
opted-in requests. Non-streaming bodies are read directly; streaming responses
are wrapped so the trailing frame is captured on exhaustion, or drained on
close when the SDK stops at the vendor's terminal event ([DONE], Bedrock's
metadata / invocation-metrics frame).

LangChain: invoke()/ainvoke() set response_metadata["associated_dollar_cost"];
stream()/astream() yield one extra empty chunk carrying it after the vendor's
last chunk. The Bedrock shim hides the gateway's costMetadata event from
langchain-aws. Absence means "not priced", never $0.
Drop tests with no unique failure mode (verified by mutation), fold the
counting byte streams into LazyByteStream, use one Bedrock model builder and
one llmgw_settings fixture, and move the transport swap into a helper. Add the
missing checks for the drain bound and for event-stream dispatch. Remove the
dead [DONE] guard in the SSE cost parser.
@andreitava-uip
andreitava-uip force-pushed the feat/keep-llmgw-dollar-cost branch from d75f0e0 to 128681e Compare September 9, 2026 11:39
@andreitava-uip
andreitava-uip merged commit 1904dee into main Sep 9, 2026
20 of 22 checks passed
@andreitava-uip
andreitava-uip deleted the feat/keep-llmgw-dollar-cost branch September 9, 2026 12:57
Sign up for free to 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.

2 participants