Skip to content

✨(back) limit output token per message#458

Open
maxenceh wants to merge 1 commit into
mainfrom
maxenceh/limit-output-tokens
Open

✨(back) limit output token per message#458
maxenceh wants to merge 1 commit into
mainfrom
maxenceh/limit-output-tokens

Conversation

@maxenceh

@maxenceh maxenceh commented May 7, 2026

Copy link
Copy Markdown
Collaborator

Purpose

Adds a configurable output token limit per AI message to control LLM costs on very long responses. When the limit is reached, the backend stops generation and the frontend displays an error message to
the user.

Proposal

Backend

  • Adds a new setting to configure the token limit
  • Enforces the output token limit during AI response generation

Frontend

  • New component that renders the "response was truncated" message shown to the user
  • Integrates TruncatedResponseMessage into the message rendering

Demo

Screen.Recording.2026-07-14.at.09.34.34.mov

Summary by CodeRabbit

Summary by CodeRabbit

Release Notes

  • New Features

    • Implemented an output token limit per message; responses that hit the limit are automatically truncated and marked for the UI
    • Added a UI notification/message to clearly indicate when a response was truncated due to the maximum token limit
  • Documentation

    • Documented LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE for configuring the per-message maximum output tokens and its default/truncation behavior

@coderabbitai

coderabbitai Bot commented May 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@maxenceh, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 35 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d019fb27-2011-416d-97a0-e07491268f7c

📥 Commits

Reviewing files that changed from the base of the PR and between 05d0882 and e3466d5.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • docs/env.md
  • src/backend/chat/agents/base.py
  • src/backend/chat/clients/pydantic_ai.py
  • src/backend/chat/tests/agents/test_base_agent.py
  • src/backend/chat/tests/clients/pydantic_ai/test_output_token_limit.py
  • src/backend/chat/tests/test_ai_agent_service_co2.py
  • src/backend/conversations/settings.py
  • src/frontend/apps/conversations/src/features/chat/components/MessageItem.tsx
  • src/frontend/apps/conversations/src/features/chat/components/TruncatedResponseMessage.tsx
  • src/frontend/apps/conversations/src/features/chat/components/__tests__/MessageItem.test.tsx

Walkthrough

This PR adds a configurable per-message LLM output token limit. The backend passes the limit to model execution, detects length-based truncation, and propagates annotations to streamed and persisted messages. The frontend renders a localized truncation notice.

Changes

Output Token Limit Feature

Layer / File(s) Summary
Configuration and documentation
src/backend/conversations/settings.py, docs/env.md, CHANGELOG.md
Adds the environment-backed LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE setting with an 8192 default, documents it, and records the feature in the changelog.
Model token-limit wiring
src/backend/chat/agents/base.py, src/backend/chat/clients/pydantic_ai.py, src/backend/chat/tests/agents/test_base_agent.py
Passes the configured limit through Pydantic-AI ModelSettings and OpenAI-compatible model extra_body.max_tokens, with coverage for OpenAI model initialization.
Backend truncation tracking and propagation
src/backend/chat/clients/pydantic_ai.py
Tracks model finish reasons through hooks, resets state during cleanup, and emits truncation annotations in streaming and persisted conversation output.
Backend validation
src/backend/chat/tests/clients/pydantic_ai/test_output_token_limit.py, src/backend/chat/tests/test_ai_agent_service_co2.py
Tests configuration, state initialization and cleanup, model settings propagation, and truncation annotations for streaming, non-streaming, and non-truncated responses.
Frontend truncation feedback
src/frontend/apps/conversations/src/features/chat/components/TruncatedResponseMessage.tsx, src/frontend/apps/conversations/src/features/chat/components/MessageItem.tsx, src/frontend/apps/conversations/src/features/chat/components/__tests__/MessageItem.test.tsx
Adds localized truncation feedback, detects annotations in assistant messages, updates memoization, and tests conditional rendering.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • suitenumerique/conversations#530: Both changes modify the Pydantic-AI agent execution path, with this PR adding output-token truncation and the other adding context trimming.

Suggested labels: 🔍 Design check

Suggested reviewers: providenz, qbey

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding a backend per-message output token limit.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch maxenceh/limit-output-tokens

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.

❤️ Share

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

@maxenceh
maxenceh force-pushed the maxenceh/limit-output-tokens branch 2 times, most recently from d8924a2 to fbabc9b Compare May 13, 2026 12:22
@maxenceh
maxenceh marked this pull request as ready for review May 13, 2026 12:25

@coderabbitai coderabbitai Bot 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: 2

🤖 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 `@src/backend/chat/tests/clients/pydantic_ai/test_output_token_limit.py`:
- Around line 20-29: The fixture base_settings is forcing
LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE to 8192 for every test, making the "default is
8192" assertions tautological; remove or stop setting
settings.LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE in the base_settings fixture and
instead set that value only in tests that need it (or create a separate fixture
for tests that require a non-default), and update the tests that assert the
default (the "default is 8192" test and the other assertions flagged) to rely on
the real unset default value so they can detect regressions; locate references
to settings.LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE and the pytest fixture
base_settings to implement this change.

In `@src/backend/conversations/settings.py`:
- Around line 681-685: ConversationsSettings defines
LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE but doesn't validate it; add a fail-fast check
in the class's post_setup method that reads
self.LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE and raises a ValueError (or logger +
sys.exit) if the value is <= 0, with a clear message identifying
LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE so startup fails early and prevents invalid
token limits from propagating into generation settings.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a3b26a1d-96d2-470e-9953-ddf14d477e3f

📥 Commits

Reviewing files that changed from the base of the PR and between 5ca4ef9 and fbabc9b.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • docs/env.md
  • src/backend/chat/clients/pydantic_ai.py
  • src/backend/chat/tests/clients/pydantic_ai/test_output_token_limit.py
  • src/backend/chat/tests/test_ai_agent_service_co2.py
  • src/backend/conversations/settings.py
  • src/frontend/apps/conversations/src/features/chat/components/MessageItem.tsx
  • src/frontend/apps/conversations/src/features/chat/components/TruncatedResponseMessage.tsx
  • src/frontend/apps/conversations/src/features/chat/components/__tests__/MessageItem.test.tsx

Comment thread src/backend/conversations/settings.py
@maxenceh
maxenceh force-pushed the maxenceh/limit-output-tokens branch from fbabc9b to b16bf5c Compare May 13, 2026 13:25
@sonarqubecloud

Copy link
Copy Markdown

@maxenceh
maxenceh force-pushed the maxenceh/limit-output-tokens branch 2 times, most recently from f911021 to 05d0882 Compare July 14, 2026 07:37

@coderabbitai coderabbitai Bot 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

♻️ Duplicate comments (1)
src/backend/chat/tests/clients/pydantic_ai/test_output_token_limit.py (1)

21-29: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

The default value test is still tautological.

The previous review flagged that forcing the setting in the base_settings fixture masks the actual default. Instead of removing it from the fixture, the value was simply changed to 1000 in both the fixture and the test assertion. This means the test is still just verifying that the fixture successfully overrode the value, rather than ensuring the application's actual default is correctly set to 8192.

Please remove the override from the fixture and assert the true default value.

🐛 Proposed fix
 `@pytest.fixture`(autouse=True)
 def base_settings(settings):
     """Set up base settings for all tests in this module."""
     settings.AI_BASE_URL = "https://api.llm.com/v1/"
     settings.AI_API_KEY = "test-key"
     settings.AI_MODEL = "model-123"
     settings.AI_AGENT_INSTRUCTIONS = "You are a helpful assistant"
     settings.AI_AGENT_TOOLS = []
-    settings.LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE = 1000


 # ---------------------------------------------------------------------------
 # Fixtures
 # ---------------------------------------------------------------------------
...
 def test_llm_max_output_tokens_per_message_setting_exists():
     """Setting must exist with a positive integer value."""
     assert hasattr(django_settings, "LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE")
     assert isinstance(django_settings.LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE, int)
-    assert django_settings.LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE == 1000
+    assert django_settings.LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE == 8192

Also applies to: 80-84

🤖 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 `@src/backend/chat/tests/clients/pydantic_ai/test_output_token_limit.py` around
lines 21 - 29, Remove the LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE assignment from the
base_settings fixture, and update the related default-value test to assert the
application's actual default of 8192. Keep the fixture overrides for unrelated
settings unchanged and ensure the test reads the unmodified setting default.
🤖 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 `@src/backend/chat/tests/clients/pydantic_ai/test_output_token_limit.py`:
- Around line 114-117: Update test_run_agent_passes_max_tokens_model_settings to
explicitly set settings.LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE to 1000 before
invoking the agent, ensuring the test uses the mocked token limit rather than
the default 8192 value.

---

Duplicate comments:
In `@src/backend/chat/tests/clients/pydantic_ai/test_output_token_limit.py`:
- Around line 21-29: Remove the LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE assignment
from the base_settings fixture, and update the related default-value test to
assert the application's actual default of 8192. Keep the fixture overrides for
unrelated settings unchanged and ensure the test reads the unmodified setting
default.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2cd1308d-c0d5-4b3b-a8b6-37978b4e31f8

📥 Commits

Reviewing files that changed from the base of the PR and between fbabc9b and 05d0882.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • docs/env.md
  • src/backend/chat/agents/base.py
  • src/backend/chat/clients/pydantic_ai.py
  • src/backend/chat/tests/agents/test_base_agent.py
  • src/backend/chat/tests/clients/pydantic_ai/test_output_token_limit.py
  • src/backend/chat/tests/test_ai_agent_service_co2.py
  • src/backend/conversations/settings.py
  • src/frontend/apps/conversations/src/features/chat/components/MessageItem.tsx
  • src/frontend/apps/conversations/src/features/chat/components/TruncatedResponseMessage.tsx
  • src/frontend/apps/conversations/src/features/chat/components/__tests__/MessageItem.test.tsx
🚧 Files skipped from review as they are similar to previous changes (7)
  • docs/env.md
  • src/backend/chat/tests/test_ai_agent_service_co2.py
  • src/backend/conversations/settings.py
  • src/frontend/apps/conversations/src/features/chat/components/MessageItem.tsx
  • src/frontend/apps/conversations/src/features/chat/components/tests/MessageItem.test.tsx
  • src/frontend/apps/conversations/src/features/chat/components/TruncatedResponseMessage.tsx
  • src/backend/chat/clients/pydantic_ai.py

Comment on lines +114 to +117
@pytest.mark.asyncio
async def test_run_agent_passes_max_tokens_model_settings(ui_messages):
"""_run_agent must pass
ModelSettings(max_tokens=LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE) to agent.iter()."""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Explicitly configure the token limit for this test.

Once the global override is removed from base_settings, this test needs to explicitly set settings.LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE = 1000 to ensure it continues to pass with the mocked value instead of falling back to the 8192 default.

🐛 Proposed fix
 `@pytest.mark.asyncio`
-async def test_run_agent_passes_max_tokens_model_settings(ui_messages):
+async def test_run_agent_passes_max_tokens_model_settings(settings, ui_messages):
     """_run_agent must pass
     ModelSettings(max_tokens=LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE) to agent.iter()."""
+    settings.LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE = 1000
     conversation = await sync_to_async(ChatConversationFactory)()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@pytest.mark.asyncio
async def test_run_agent_passes_max_tokens_model_settings(ui_messages):
"""_run_agent must pass
ModelSettings(max_tokens=LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE) to agent.iter()."""
`@pytest.mark.asyncio`
async def test_run_agent_passes_max_tokens_model_settings(settings, ui_messages):
"""_run_agent must pass
ModelSettings(max_tokens=LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE) to agent.iter()."""
settings.LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE = 1000
🤖 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 `@src/backend/chat/tests/clients/pydantic_ai/test_output_token_limit.py` around
lines 114 - 117, Update test_run_agent_passes_max_tokens_model_settings to
explicitly set settings.LLM_MAX_OUTPUT_TOKENS_PER_MESSAGE to 1000 before
invoking the agent, ensuring the test uses the mocked token limit rather than
the default 8192 value.

If limit is reached display a message to the user.
It's meant to limit cost of very long messages, so the
limit must be high.
@maxenceh
maxenceh force-pushed the maxenceh/limit-output-tokens branch from 05d0882 to e3466d5 Compare July 14, 2026 08:03
@maxenceh
maxenceh requested a review from providenz July 14, 2026 08:03
@sonarqubecloud

Copy link
Copy Markdown

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.

1 participant