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, request: Request,
config: AppConfig = Depends(get_config), config: AppConfig = Depends(get_config),
) -> SuggestionsResponse: ) -> SuggestionsResponse:
if not config.suggestions.enabled:
return SuggestionsResponse(suggestions=[])
if not body.messages: if not body.messages:
return SuggestionsResponse(suggestions=[]) 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.skills_config import SkillsConfig
from deerflow.config.stream_bridge_config import StreamBridgeConfig, load_stream_bridge_config_from_dict 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.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.summarization_config import SummarizationConfig, load_summarization_config_from_dict
from deerflow.config.title_config import TitleConfig, load_title_config_from_dict from deerflow.config.title_config import TitleConfig, load_title_config_from_dict
from deerflow.config.token_usage_config import TokenUsageConfig 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") acp_agents: dict[str, ACPAgentConfig] = Field(default_factory=dict, description="ACP-compatible agent configuration")
subagents: SubagentsAppConfig = Field(default_factory=SubagentsAppConfig, description="Subagent runtime configuration") subagents: SubagentsAppConfig = Field(default_factory=SubagentsAppConfig, description="Subagent runtime configuration")
guardrails: GuardrailsConfig = Field(default_factory=GuardrailsConfig, description="Guardrail middleware 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") circuit_breaker: CircuitBreakerConfig = Field(default_factory=CircuitBreakerConfig, description="LLM circuit breaker configuration")
channel_connections: ChannelConnectionsConfig = Field( channel_connections: ChannelConnectionsConfig = Field(
default_factory=ChannelConnectionsConfig, 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)) fake_model.ainvoke = AsyncMock(return_value=MagicMock(content=content))
monkeypatch.setattr(suggestions, "create_chat_model", lambda **kwargs: fake_model) 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 == ["深度学习和机器学习的区别?", "常用框架有哪些?", "需要什么数学基础?"] assert result.suggestions == ["深度学习和机器学习的区别?", "常用框架有哪些?", "需要什么数学基础?"]
@@ -103,7 +103,7 @@ def test_generate_suggestions_parses_and_limits(monkeypatch):
# Bypass the require_permission decorator (which needs request + # Bypass the require_permission decorator (which needs request +
# thread_store) — these tests cover the parsing logic. # 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"] assert result.suggestions == ["Q1", "Q2", "Q3"]
fake_model.ainvoke.assert_awaited_once() 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 + # Bypass the require_permission decorator (which needs request +
# thread_store) — these tests cover the parsing logic. # 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"] assert result.suggestions == ["Q1", "Q2"]
fake_model.ainvoke.assert_awaited_once() 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 + # Bypass the require_permission decorator (which needs request +
# thread_store) — these tests cover the parsing logic. # 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"] assert result.suggestions == ["Q1", "Q2"]
fake_model.ainvoke.assert_awaited_once() 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 + # Bypass the require_permission decorator (which needs request +
# thread_store) — these tests cover the parsing logic. # 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 == [] 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()
+11 -1
View File
@@ -15,7 +15,7 @@
# ============================================================================ # ============================================================================
# Bump this number when the config schema changes. # Bump this number when the config schema changes.
# Run `make config-upgrade` to merge new fields into your local config.yaml. # Run `make config-upgrade` to merge new fields into your local config.yaml.
config_version: 12 config_version: 13
# ============================================================================ # ============================================================================
# Logging # Logging
@@ -711,6 +711,16 @@ tool_output:
# web_search: 8000 # web_search: 8000
# bash: 20000 # bash: 20000
# ============================================================================
# Suggestions Configuration
# ============================================================================
# Configure whether the agent automatically generates follow-up question
# suggestions at the end of each response.
suggestions:
enabled: true
# ============================================================================ # ============================================================================
# Loop Detection Configuration # Loop Detection Configuration
# ============================================================================ # ============================================================================