mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-08 04:22:20 +08:00
## Summary - Treat `max_tokens=0` as unset (`or 8192`) when building model context budgets, fixing agents that silently zeroed prompts when a vLLM model had `max_tokens: 0` in tenant config - Replace trailing same-role canvas history in `LLM._sys_prompt_and_msg` instead of skipping the current user prompt - Add `LLM.fit_messages()` validation after `message_fit_in` on agent paths so empty user content fails fast with a clear error instead of reaching vLLM Fixes #16411 ## Root cause Agent canvas workflow called `message_fit_in` with `int(max_length * 0.97)`. When `max_length` was `0`, both system and user content were trimmed to empty strings. The `[HISTORY STREAMLY]` log showing only `{"role":"user","content":""}` matches this. A secondary bug skipped appending the formatted user prompt when history ended with a `user` role message. ## Test plan - [x] Added `test/unit_test/agent/component/test_llm_prompt.py` for role-replace, validation, and zero-budget fitting - [x] Added `test_message_fit_in_zero_budget_preserves_non_empty_messages` in `test_generator_message_fit_in.py` - [ ] CI unit tests - [ ] Manual: agent canvas `begin → Retrieval → Agent → Message` with vLLM Qwen3; confirm user message reaches LLM Made with [Cursor](https://cursor.com) --------- Co-authored-by: Taranum Wasu <taranumwasu@Taranums-MacBook-Pro.local> Co-authored-by: Cursor <cursoragent@cursor.com>
62 lines
1.8 KiB
Python
62 lines
1.8 KiB
Python
import pytest
|
|
|
|
from agent.component.llm import LLM, LLMParam
|
|
|
|
|
|
def _llm(prompts=None):
|
|
cpn = LLM.__new__(LLM)
|
|
cpn._param = LLMParam()
|
|
cpn._param.prompts = prompts or [{"role": "user", "content": "User query: {sys.query}"}]
|
|
return cpn
|
|
|
|
|
|
@pytest.mark.p1
|
|
def test_sys_prompt_and_msg_replaces_trailing_user_instead_of_skipping():
|
|
cpn = _llm()
|
|
msg, _ = cpn._sys_prompt_and_msg([{"role": "user", "content": ""}], {"sys.query": "test"})
|
|
assert msg == [{"role": "user", "content": "User query: test"}]
|
|
|
|
|
|
@pytest.mark.p1
|
|
def test_sys_prompt_and_msg_keeps_consecutive_configured_prompts():
|
|
cpn = _llm(
|
|
[
|
|
{"role": "user", "content": "Context: {sys.query}"},
|
|
{"role": "user", "content": "User query: {sys.query}"},
|
|
]
|
|
)
|
|
msg, _ = cpn._sys_prompt_and_msg([], {"sys.query": "test"})
|
|
assert msg == [
|
|
{"role": "user", "content": "Context: test"},
|
|
{"role": "user", "content": "User query: test"},
|
|
]
|
|
|
|
|
|
@pytest.mark.p1
|
|
def test_validate_fitted_messages_requires_trailing_user():
|
|
err = LLM.validate_fitted_messages(
|
|
[
|
|
{"role": "system", "content": "system"},
|
|
{"role": "user", "content": "still here"},
|
|
{"role": "assistant", "content": "reply"},
|
|
]
|
|
)
|
|
assert err and "empty" in err.lower()
|
|
|
|
|
|
@pytest.mark.p1
|
|
def test_validate_fitted_messages_rejects_empty_user():
|
|
err = LLM.validate_fitted_messages([{"role": "system", "content": "system"}, {"role": "user", "content": ""}])
|
|
assert err and "empty" in err.lower()
|
|
|
|
|
|
@pytest.mark.p1
|
|
def test_fit_messages_uses_default_context_when_max_length_zero():
|
|
msg_fit, err = LLM.fit_messages(
|
|
"s" * 845,
|
|
[{"role": "user", "content": "User query: test"}],
|
|
0,
|
|
)
|
|
assert err is None
|
|
assert msg_fit[-1]["content"] == "User query: test"
|