mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-06-15 11:56:01 +00:00
Compare commits
1 Commits
2.0.0-release
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d2cc991d55 |
@@ -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")
|
||||
@@ -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()
|
||||
|
||||
+11
-1
@@ -15,7 +15,7 @@
|
||||
# ============================================================================
|
||||
# Bump this number when the config schema changes.
|
||||
# Run `make config-upgrade` to merge new fields into your local config.yaml.
|
||||
config_version: 12
|
||||
config_version: 13
|
||||
|
||||
# ============================================================================
|
||||
# Logging
|
||||
@@ -711,6 +711,16 @@ tool_output:
|
||||
# web_search: 8000
|
||||
# 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
|
||||
# ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user