Add model context window metadata and proactive token-limit check - #264
Add model context window metadata and proactive token-limit check#264the-hercules wants to merge 2 commits into
Conversation
- Introduced TokenCounterInterface and HeuristicTokenCounter for token estimation. - Implemented context window checks in PromptBuilder to prevent exceeding model limits. - Enhanced ModelMetadata to include context window property. - Updated tests to validate new functionality and ensure proper exception handling.
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## trunk #264 +/- ##
============================================
+ Coverage 86.49% 86.60% +0.11%
- Complexity 1327 1344 +17
============================================
Files 68 69 +1
Lines 4295 4353 +58
============================================
+ Hits 3715 3770 +55
- Misses 580 583 +3
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@the-hercules Is this missing an AI Use disclosure in the PR? (What models were used, how, caveats, etc) |
|
@georgestephanis Yes, it was missing AI disclosure, it was my first time raising PR on this repo so didn't know that we write a disclosure at the end. Will keep in mind from next time. |
|
Thanks! |

Closes - #260
Summary
Adds an input-side counterpart to the existing output-side
maxTokenscap. Today the SDK has no concept of a model's context window, andTokenLimitReachedExceptionis declared but never thrown. This PR lets callers (chat UIs, agents, plugins) discover a model's context window and be told — with a typed, catchable exception, before a request is sent — when an assembled prompt is estimated to be too large, instead of relying on an opaque provider-side HTTP error.Implements the input-side of #260 (context-window metadata + proactive budgeting). The reactive/output-side (#193) is intentionally left as a follow-up; both can share the same
TokenLimitReachedException.What changed
Metadata
ModelMetadatagains an optionalcontextWindow(?int, total input + output tokens). Nullable, defaults tonull, backward compatible: constructor param is trailing/optional, included intoArray()/schema only when set, read infromArray()without becoming required. Rejected if< 1.Token counting (BYOB)
TokenCounterInterface(countTokens(list<Message>, ModelMetadata): int).HeuristicTokenCounter: a rough, dependency-freestrlen / 4estimate over text parts, documented as advisory. Non-text parts (files, tool calls/responses) are not counted.PromptBuilder::usingTokenCounter()to inject an accurate, model-specific counter per request.Proactive check
PromptBuilder::generateResult()now estimates the prompt (message text + system instruction, plus the configured outputmaxTokenswhen set) and throwsTokenLimitReachedExceptionwhen the total exceeds the resolved model'scontextWindow. No-op when the context window is unknown, so existing behavior is preserved until providers populate the field.Docs
contextWindow(input budget) vsModelConfig::maxTokens(output cap) in docblocks,docs/ARCHITECTURE.md, anddocs/GLOSSARY.md.Design notes / assumptions
contextWindowis the total window (input + output), matching published specs — not input-only.usingTokenCounter()is the escape hatch, and the docblock states a passing check is not a guarantee.HttpTransporter-style parity) is deliberately out of scope here.null.Relationship to #193
#193 covers the reactive/output side (truncation via
finish_reason == length). This PR is the proactive/input side. The exception and its docblock cover both cases so #193 can build on this; thelengthfinish reason already flows through toCandidate::getFinishReason()->isLength().Testing
composer lint(PHPCS PSR-12/PER + Slevomat, and PHPStan levelmax): clean.composer test:unit: full suite passes, including new tests for thecontextWindowfield,HeuristicTokenCounter, and thePromptBuildercheck (throws when over, no-op when the window is unknown, honors reserved output, honors an injected counter).AI usage disclosure
This PR was developed collaboratively with Claude, in a Claude Code session, under human direction and review.
claude-opus-4-8), via Claude Code.ModelMetadata,ModelConfig,TokenLimitReachedException,PromptBuilder) and reviewed related issue Missing Required Parameters in Tool Calls When Token Limit Reached #193.HttpTransporter-style override mechanism, exception/finish_reasonhandling, test structure, PHP 7.4 / PHPStan-max / PSR-12 constraints) before writing code.ARCHITECTURE.md,GLOSSARY.md), and wrote this PR description and the issue comment.composer lint(PHPCS + PHPStan levelmax) andcomposer test:unit, plus an offline demo script, to verify behavior.