Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/backend/agents/agent_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions src/backend/orchestration/plan_review_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment thread
Ayaz-Microsoft marked this conversation as resolved.
- 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
Expand Down
30 changes: 30 additions & 0 deletions src/tests/backend/agents/test_agent_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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)
Expand All @@ -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,
)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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'."""
Expand Down
6 changes: 6 additions & 0 deletions src/tests/backend/orchestration/test_plan_review_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down