diff --git a/src/backend/agents/agent_factory.py b/src/backend/agents/agent_factory.py index c27bc2106..cd7de8a90 100644 --- a/src/backend/agents/agent_factory.py +++ b/src/backend/agents/agent_factory.py @@ -55,6 +55,13 @@ class UnsupportedModelError(Exception): 6. Do NOT re-ask anything already answered in the conversation history. """ +_KNOWLEDGE_BASE_NO_CITATIONS_PROMPT = """ + +RESPONSE CITATION POLICY (CRITICAL): +- Do not include any citation markers, source-reference tokens, attribution + markers, or footnotes in your response. +""" + class AgentFactory: """Create and manage teams of agents from JSON configuration. @@ -158,6 +165,9 @@ async def create_agent_from_config( # Build agent instructions from system_message + optional interaction rules instructions = getattr(agent_obj, "system_message", "") + if kb_config: + instructions += _KNOWLEDGE_BASE_NO_CITATIONS_PROMPT + # Universal user-interaction rules for agents that have # user_responses=true — tells them to call request_user_clarification. if user_responses: diff --git a/src/backend/orchestration/plan_review_helpers.py b/src/backend/orchestration/plan_review_helpers.py index efb2d536e..2effb598d 100644 --- a/src/backend/orchestration/plan_review_helpers.py +++ b/src/backend/orchestration/plan_review_helpers.py @@ -180,6 +180,8 @@ def get_magentic_prompt_kwargs( recommend, or guess any specific team, do NOT claim any action was performed, and do NOT attempt to answer the out-of-scope request itself. - Compile ONLY from messages agents actually produced. Quote verbatim where appropriate. +- Do not include any citation markers, source-reference tokens, attribution + markers, or footnotes in your response. - Do NOT fabricate URLs, results, or content that no agent produced. - If a required agent step did not run, state it plainly — do not pretend it did. - If an agent produced an image (a markdown image ![alt](url) or an image URL such as one diff --git a/src/tests/backend/agents/test_agent_factory.py b/src/tests/backend/agents/test_agent_factory.py index 77f03d62b..e483e5054 100644 --- a/src/tests/backend/agents/test_agent_factory.py +++ b/src/tests/backend/agents/test_agent_factory.py @@ -46,6 +46,7 @@ # --- agents sub-modules (short absolute imports in factory code) mock_agent_template_cls = Mock() mock_mcp_config_cls = Mock() +mock_knowledge_base_config_cls = Mock() sys.modules.setdefault("agents", Mock()) # parent package stub _mock_agent_template_mod = Mock() @@ -57,6 +58,7 @@ _mock_mcp_config_mod = Mock() _mock_mcp_config_mod.MCPConfig = mock_mcp_config_cls _mock_mcp_config_mod.VectorStoreConfig = mock_vector_store_config_cls +_mock_mcp_config_mod.KnowledgeBaseConfig = mock_knowledge_base_config_cls sys.modules["config.mcp_config"] = _mock_mcp_config_mod # Now import the module under test (full backend.* path as per project convention) @@ -76,6 +78,8 @@ def _agent_obj(**overrides) -> SimpleNamespace: coding_tools=False, use_toolbox=False, use_file_search=False, + use_knowledge_base=False, + knowledge_base_name=None, user_responses=False, vector_store_name=None, ) @@ -113,6 +117,7 @@ def setup_method(self): self.memory_store = Mock() mock_agent_template_cls.reset_mock() mock_mcp_config_cls.reset_mock() + mock_knowledge_base_config_cls.reset_mock() mock_vector_store_config_cls.reset_mock() @pytest.mark.asyncio @@ -164,6 +169,31 @@ async def test_user_responses_false_no_mcp_config(self): mock_mcp_config_cls.from_env.assert_not_called() + @pytest.mark.asyncio + async def test_knowledge_base_agent_appends_no_citations_prompt(self): + """KB-backed agents receive citation cleanup instructions.""" + kb_instance = Mock() + mock_knowledge_base_config_cls.from_env.return_value = kb_instance + agent_instance = Mock() + agent_instance.open = AsyncMock() + mock_agent_template_cls.return_value = agent_instance + + await self.factory.create_agent_from_config( + "user123", + _agent_obj( + use_knowledge_base=True, + knowledge_base_name="test-kb", + system_message="Use retrieved facts.", + ), + self.team_config, + self.memory_store, + ) + + mock_knowledge_base_config_cls.from_env.assert_called_once_with("test-kb") + instructions = mock_agent_template_cls.call_args[1]["agent_instructions"] + assert "RESPONSE CITATION POLICY" in instructions + assert "Do not include any citation markers" in instructions + @pytest.mark.asyncio async def test_use_toolbox_takes_priority_over_user_responses(self): """use_toolbox=True takes priority; MCPConfig uses the toolbox_filter, not 'user_responses'.""" diff --git a/src/tests/backend/orchestration/test_plan_review_helpers.py b/src/tests/backend/orchestration/test_plan_review_helpers.py index 9bc9a595d..2ebf03047 100644 --- a/src/tests/backend/orchestration/test_plan_review_helpers.py +++ b/src/tests/backend/orchestration/test_plan_review_helpers.py @@ -237,6 +237,12 @@ def test_given_no_user_responses_when_called_then_final_has_answer_rules(self): # Assert assert "FINAL ANSWER RULES" in result["final_answer_prompt"] + def test_given_any_team_when_called_then_final_suppresses_citations(self): + result = get_magentic_prompt_kwargs(has_user_responses=False) + + final_prompt = result["final_answer_prompt"] + assert "Do not include any citation markers" in final_prompt + def test_given_default_when_called_then_user_responses_is_false(self): # Act result = get_magentic_prompt_kwargs()