make ai follow-up suggestions optional (#3591)

This commit is contained in:
Chetan Sharma
2026-06-15 15:29:25 +05:30
committed by GitHub
parent 25fbd25b05
commit d2cc991d55
5 changed files with 50 additions and 6 deletions
@@ -135,6 +135,8 @@ async def generate_suggestions(
request: Request,
config: AppConfig = Depends(get_config),
) -> SuggestionsResponse:
if not config.suggestions.enabled:
return SuggestionsResponse(suggestions=[])
if not body.messages:
return SuggestionsResponse(suggestions=[])
@@ -28,6 +28,7 @@ from deerflow.config.skill_evolution_config import SkillEvolutionConfig
from deerflow.config.skills_config import SkillsConfig
from deerflow.config.stream_bridge_config import StreamBridgeConfig, load_stream_bridge_config_from_dict
from deerflow.config.subagents_config import SubagentsAppConfig, load_subagents_config_from_dict
from deerflow.config.suggestions_config import SuggestionsConfig
from deerflow.config.summarization_config import SummarizationConfig, load_summarization_config_from_dict
from deerflow.config.title_config import TitleConfig, load_title_config_from_dict
from deerflow.config.token_usage_config import TokenUsageConfig
@@ -116,6 +117,7 @@ class AppConfig(BaseModel):
acp_agents: dict[str, ACPAgentConfig] = Field(default_factory=dict, description="ACP-compatible agent configuration")
subagents: SubagentsAppConfig = Field(default_factory=SubagentsAppConfig, description="Subagent runtime configuration")
guardrails: GuardrailsConfig = Field(default_factory=GuardrailsConfig, description="Guardrail middleware configuration")
suggestions: SuggestionsConfig = Field(default_factory=SuggestionsConfig, description="Follow-up suggestions configuration.")
circuit_breaker: CircuitBreakerConfig = Field(default_factory=CircuitBreakerConfig, description="LLM circuit breaker configuration")
channel_connections: ChannelConnectionsConfig = Field(
default_factory=ChannelConnectionsConfig,
@@ -0,0 +1,7 @@
from pydantic import BaseModel, Field
class SuggestionsConfig(BaseModel):
"""Configuration for automatic follow-up suggestions."""
enabled: bool = Field(default=True, description="Whether to enable follow-up question suggestions at the end of an AI response")
+28 -5
View File
@@ -74,7 +74,7 @@ def test_generate_suggestions_strips_inline_think_block(monkeypatch):
fake_model.ainvoke = AsyncMock(return_value=MagicMock(content=content))
monkeypatch.setattr(suggestions, "create_chat_model", lambda **kwargs: fake_model)
result = asyncio.run(suggestions.generate_suggestions.__wrapped__("t1", req, request=None, config=SimpleNamespace()))
result = asyncio.run(suggestions.generate_suggestions.__wrapped__("t1", req, request=None, config=SimpleNamespace(suggestions=SimpleNamespace(enabled=True))))
assert result.suggestions == ["深度学习和机器学习的区别?", "常用框架有哪些?", "需要什么数学基础?"]
@@ -103,7 +103,7 @@ def test_generate_suggestions_parses_and_limits(monkeypatch):
# Bypass the require_permission decorator (which needs request +
# thread_store) — these tests cover the parsing logic.
result = asyncio.run(suggestions.generate_suggestions.__wrapped__("t1", req, request=None, config=SimpleNamespace()))
result = asyncio.run(suggestions.generate_suggestions.__wrapped__("t1", req, request=None, config=SimpleNamespace(suggestions=SimpleNamespace(enabled=True))))
assert result.suggestions == ["Q1", "Q2", "Q3"]
fake_model.ainvoke.assert_awaited_once()
@@ -125,7 +125,7 @@ def test_generate_suggestions_parses_list_block_content(monkeypatch):
# Bypass the require_permission decorator (which needs request +
# thread_store) — these tests cover the parsing logic.
result = asyncio.run(suggestions.generate_suggestions.__wrapped__("t1", req, request=None, config=SimpleNamespace()))
result = asyncio.run(suggestions.generate_suggestions.__wrapped__("t1", req, request=None, config=SimpleNamespace(suggestions=SimpleNamespace(enabled=True))))
assert result.suggestions == ["Q1", "Q2"]
fake_model.ainvoke.assert_awaited_once()
@@ -147,7 +147,7 @@ def test_generate_suggestions_parses_output_text_block_content(monkeypatch):
# Bypass the require_permission decorator (which needs request +
# thread_store) — these tests cover the parsing logic.
result = asyncio.run(suggestions.generate_suggestions.__wrapped__("t1", req, request=None, config=SimpleNamespace()))
result = asyncio.run(suggestions.generate_suggestions.__wrapped__("t1", req, request=None, config=SimpleNamespace(suggestions=SimpleNamespace(enabled=True))))
assert result.suggestions == ["Q1", "Q2"]
fake_model.ainvoke.assert_awaited_once()
@@ -166,6 +166,29 @@ def test_generate_suggestions_returns_empty_on_model_error(monkeypatch):
# Bypass the require_permission decorator (which needs request +
# thread_store) — these tests cover the parsing logic.
result = asyncio.run(suggestions.generate_suggestions.__wrapped__("t1", req, request=None, config=SimpleNamespace()))
result = asyncio.run(suggestions.generate_suggestions.__wrapped__("t1", req, request=None, config=SimpleNamespace(suggestions=SimpleNamespace(enabled=True))))
assert result.suggestions == []
def test_generate_suggestions_returns_empty_when_disabled(monkeypatch):
"""Ensure suggestions are bypassed and returned an empty list when disabled in config."""
req = suggestions.SuggestionsRequest(
messages=[
suggestions.SuggestionMessage(role="user", content="Hi"),
suggestions.SuggestionMessage(role="assistant", content="Hello"),
],
n=3,
model_name=None,
)
mock_config = SimpleNamespace(suggestions=SimpleNamespace(enabled=False))
fake_model = MagicMock()
fake_model.ainvoke = AsyncMock(side_effect=RuntimeError("Model should not be called."))
monkeypatch.setattr(suggestions, "create_chat_model", lambda **kwargs: fake_model)
result = asyncio.run(suggestions.generate_suggestions.__wrapped__("t1", req, request=None, config=mock_config))
assert result.suggestions == []
fake_model.ainvoke.assert_not_called()