You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Add provenance field to audit payload to distinguish missing vs. genuinely-zero prompt token counts #1953
In PR #1895 (Add a dedicated OpenAI-compatible LLM adapter), the _record_usage method in unstract/sdk1/src/unstract/sdk1/llm.py was updated to fall back to prompt_tokens=0 when:
The provider does not return usage.prompt_tokens in its response, and
LiteLLM's token_counter() raises (e.g., for unmapped custom/OpenAI-compatible models).
A warning is logged in this case, but the recorded usage payload contains prompt_llm_token_count=0 with no flag to distinguish "estimation failed / data unavailable" from "the prompt genuinely consumed zero tokens".
This was acknowledged as a known limitation and deferred from PR #1895 because adding a provenance field requires a wider end-to-end contract change across the usage pipeline beyond sdk1.
Note (post-#1929): The usage path has changed — _record_usage no longer calls Audit().push_usage_data. Usage now flows through self._pending_usage → worker bulk_create_usage. The core problem remains unchanged.
Downstream consumers of the usage data (cost attribution, analytics, billing) cannot distinguish:
Missing data — token estimation failed for an unmapped model
Genuinely zero — the prompt actually consumed zero tokens
This silently understates prompt-token consumption in cost attribution and analytics for long-running workloads against OpenAI-compatible endpoints that do not return usage.prompt_tokens.
Proposed Solution
Add a provenance / sentinel field to the usage payload, for example:
prompt_tokens_source: an enum/string such as "provider", "estimated", or "unknown"
or estimation_failed: a boolean flag
This would require changes to (re-anchored against the new shape post-#1929):
unstract/sdk1/src/unstract/sdk1/llm.py — populate the provenance field in _record_usage before appending to self._pending_usage
The OSS Usage model — extend the schema to include the provenance field
The worker bulk_create_usage path — consume and persist the new field
Downstream platform/usage-record schema — surface and store the new field
Optionally, increment an ops metric/counter when the fallback path is triggered so operations teams can detect silent drift without parsing logs.
Heads-up after #1929 merged: the problem this issue describes is still real — _record_usage continues to silently record prompt_tokens=0 when the provider omits the field and litellm.token_counter() raises, with no flag distinguishing "estimation failed" from "genuinely zero".
The Proposed Solution file list is stale, though: _record_usage no longer calls Audit().push_usage_data, TokenCounterCompat is no longer in this path, and usage now flows through self._pending_usage → worker bulk_create_usage. Whoever picks this up should re-anchor against the new shape in unstract/sdk1/src/unstract/sdk1/llm.py and the OSS Usage model rather than audit.py / utils/common.py.
#!/bin/bash
# Get the current issue description
gh issue view 1953 --repo Zipstack/unstract --json title,body,comments
# Get the specific comment
gh api repos/Zipstack/unstract/issues/comments/4419838595
Length of output: 6608
🏁 Script executed:
#!/bin/bash
gh issue edit 1953 --repo Zipstack/unstract --body "## Context
In PR `#1895` (Add a dedicated OpenAI-compatible LLM adapter), the \`_record_usage\` method in \`unstract/sdk1/src/unstract/sdk1/llm.py\` was updated to fall back to \`prompt_tokens=0\` when:
1. The provider does not return \`usage.prompt_tokens\` in its response, **and**
2. LiteLLM's \`token_counter()\` raises (e.g., for unmapped custom/OpenAI-compatible models).
A warning is logged in this case, but the recorded usage payload contains \`prompt_llm_token_count=0\` with no flag to distinguish \"estimation failed / data unavailable\" from \"the prompt genuinely consumed zero tokens\".
This was acknowledged as a known limitation and deferred from PR `#1895` because adding a provenance field requires a wider end-to-end contract change across the usage pipeline beyond sdk1.
> **Note (post-#1929):** The usage path has changed — \`_record_usage\` no longer calls \`Audit().push_usage_data\`. Usage now flows through \`self._pending_usage\` → worker \`bulk_create_usage\`. The core problem remains unchanged.
Relevant discussion: https://github.com/Zipstack/unstract/pull/1895#discussion_r3135244964
## Problem
Downstream consumers of the usage data (cost attribution, analytics, billing) cannot distinguish:
- **Missing data** — token estimation failed for an unmapped model
- **Genuinely zero** — the prompt actually consumed zero tokens
This silently understates prompt-token consumption in cost attribution and analytics for long-running workloads against OpenAI-compatible endpoints that do not return \`usage.prompt_tokens\`.
## Proposed Solution
Add a provenance / sentinel field to the usage payload, for example:
- \`prompt_tokens_source\`: an enum/string such as \`\"provider\"\`, \`\"estimated\"\`, or \`\"unknown\"\`
- or \`estimation_failed\`: a boolean flag
This would require changes to (re-anchored against the new shape post-#1929):
1. \`unstract/sdk1/src/unstract/sdk1/llm.py\` — populate the provenance field in \`_record_usage\` before appending to \`self._pending_usage\`
2. The OSS \`Usage\` model — extend the schema to include the provenance field
3. The worker \`bulk_create_usage\` path — consume and persist the new field
4. Downstream platform/usage-record schema — surface and store the new field
Optionally, increment an ops metric/counter when the fallback path is triggered so operations teams can detect silent drift without parsing logs.
## References
- PR `#1895` — Add a dedicated OpenAI-compatible LLM adapter
- PR `#1929` — Changed usage flow from \`Audit().push_usage_data\` to \`self._pending_usage\` → \`bulk_create_usage\`
- Related issues: `#1894`, `#856`, `#1443`
- Opened by: `@hari-kuriakose`"
Length of output: 49
@ritwik-g, the description of #1953 has been updated. Here's a summary of what changed:
Proposed Solution file list — Removed the stale TokenCounterCompat / utils/common.py and audit.py entries, and re-anchored the steps against the new shape:
llm.py → populate provenance in _record_usage before appending to self._pending_usage
OSS Usage model — extend schema
Worker bulk_create_usage path — consume and persist
Downstream platform schema — surface and store
References — Added PR #1929 to document the usage-flow change.
The proposed prompt_tokens_source enum approach looks right. One concrete suggestion on the schema shape:
classPromptTokensSource(str, Enum):
provider="provider"# usage.prompt_tokens returned by the providerestimated="estimated"# token_counter() succeeded as a fallbackunknown="unknown"# token_counter() also failed; value is 0
Using a string enum (rather than a bool estimation_failed) lets you add estimated as an intermediate state, which matters if you later want to distinguish "we guessed" from "we have no idea". The unknown case is the one that today silently writes 0, so downstream cost attribution can filter or flag those rows specifically.
For the bulk_create_usage path post-#1929: the field only needs to survive the worker → DB write. If the DB column is nullable varchar, you can add it with a default of null (mapped to unknown in code) and backfill historical rows as null without a breaking migration.
Activity
chandrasekharan-zipstack commentedon May 11, 2026
Heads-up after #1929 merged: the problem this issue describes is still real —
_record_usagecontinues to silently recordprompt_tokens=0when the provider omits the field andlitellm.token_counter()raises, with no flag distinguishing "estimation failed" from "genuinely zero".The Proposed Solution file list is stale, though:
_record_usageno longer callsAudit().push_usage_data,TokenCounterCompatis no longer in this path, and usage now flows throughself._pending_usage→ workerbulk_create_usage. Whoever picks this up should re-anchor against the new shape inunstract/sdk1/src/unstract/sdk1/llm.pyand the OSSUsagemodel rather thanaudit.py/utils/common.py.ritwik-g commentedon May 12, 2026
@coderabbitai can we update the description as per #1953 (comment)
coderabbitai commentedon May 12, 2026
🧩 Analysis chain
🏁 Script executed:
Length of output: 6608
🏁 Script executed:
Length of output: 49
@ritwik-g, the description of#1953has been updated. Here's a summary of what changed:Audit().push_usage_datawith a note explaining the post-UN-2946 [FEAT] Prompt Studio lookups bridge, executor hook, and IDE wiring (OSS side) #1929 flow change, and kept the core problem statement intact.TokenCounterCompat/utils/common.pyandaudit.pyentries, and re-anchored the steps against the new shape:llm.py→ populate provenance in_record_usagebefore appending toself._pending_usageUsagemodel — extend schemabulk_create_usagepath — consume and persist#1929to document the usage-flow change.cschanhniem commentedon Jun 19, 2026
The proposed
prompt_tokens_sourceenum approach looks right. One concrete suggestion on the schema shape:Using a string enum (rather than a bool
estimation_failed) lets you addestimatedas an intermediate state, which matters if you later want to distinguish "we guessed" from "we have no idea". Theunknowncase is the one that today silently writes 0, so downstream cost attribution can filter or flag those rows specifically.For the bulk_create_usage path post-#1929: the field only needs to survive the worker → DB write. If the DB column is nullable varchar, you can add it with a default of
null(mapped tounknownin code) and backfill historical rows asnullwithout a breaking migration.