test_ui_metadata.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. """
  2. Tests for UI metadata payloads and demo launcher configuration.
  3. """
  4. import os
  5. from pathlib import Path
  6. from unittest.mock import patch
  7. import pytest
  8. from agency_swarm import Agency, Agent
  9. class TestMetadataDetails:
  10. def test_get_metadata_rich_metadata(self):
  11. """Ensure metadata includes tool details and agency info."""
  12. from agents import function_tool
  13. @function_tool
  14. def sample_tool(text: str) -> str:
  15. """Echo text."""
  16. return text
  17. agent = Agent(name="ToolAgent", instructions="Use the tool", tools=[sample_tool])
  18. agency = Agency(agent, name="ToolAgency", shared_instructions="shared.md")
  19. payload = agency.get_metadata()
  20. agent_node = next(n for n in payload["nodes"] if n["id"] == "ToolAgent")
  21. data = agent_node["data"]
  22. assert data["toolCount"] == 1
  23. assert data["tools"][0]["name"] == "sample_tool"
  24. assert "inputSchema" in data["tools"][0]
  25. assert "text" in data["tools"][0]["inputSchema"].get("properties", {})
  26. data["tools"][0]["inputSchema"]["properties"]["injected"] = {"type": "string"}
  27. assert "injected" not in sample_tool.params_json_schema.get("properties", {})
  28. assert data["instructions"].startswith("shared.md")
  29. meta = payload["metadata"]
  30. assert meta["agencyName"] == "ToolAgency"
  31. assert meta["layoutAlgorithm"] == "hierarchical"
  32. assert meta["agents"] == ["ToolAgent"]
  33. def test_get_metadata_includes_quick_replies(self):
  34. agent = Agent(
  35. name="QuickRepliesAgent",
  36. instructions="Use quick replies",
  37. conversation_starters=["I need help with billing"],
  38. quick_replies=["hi", "hello"],
  39. )
  40. agency = Agency(agent)
  41. payload = agency.get_metadata()
  42. agent_node = next(n for n in payload["nodes"] if n["id"] == "QuickRepliesAgent")
  43. data = agent_node["data"]
  44. assert data["conversationStarters"] == ["I need help with billing"]
  45. assert data["quickReplies"] == ["hi", "hello"]
  46. def test_hosted_mcp_tools_unique_ids(self):
  47. """HostedMCPTool instances should produce unique tool nodes and server labels."""
  48. from agents import HostedMCPTool
  49. agent = Agent(
  50. name="SearchCoordinator",
  51. instructions="Handle searches",
  52. tools=[
  53. HostedMCPTool(
  54. tool_config={
  55. "type": "mcp",
  56. "server_label": "tavily-server",
  57. "server_url": "https://example.com/tavily",
  58. "require_approval": "never",
  59. }
  60. ),
  61. HostedMCPTool(
  62. tool_config={
  63. "type": "mcp",
  64. "server_label": "youtube-server",
  65. "server_url": "https://example.com/youtube",
  66. "require_approval": "never",
  67. }
  68. ),
  69. ],
  70. )
  71. agency = Agency(agent)
  72. structure = agency.get_agency_graph()
  73. tool_nodes = [n for n in structure["nodes"] if n["type"] == "tool"]
  74. ids = [n["id"] for n in tool_nodes]
  75. assert len(ids) == len(set(ids))
  76. labels = [n["data"]["label"] for n in tool_nodes]
  77. assert "tavily-server" in labels and "youtube-server" in labels
  78. def test_get_agency_structure_deprecated_alias(self):
  79. agency = Agency(Agent(name="CEO", instructions="test"))
  80. with pytest.warns(DeprecationWarning, match="get_agency_structure"):
  81. structure = agency.get_agency_structure()
  82. assert structure == agency.get_agency_graph()
  83. def test_copilot_demo_launcher_sets_client_facing_backend_url():
  84. """Copilot demo must not use 0.0.0.0 or trailing slash for the frontend URL."""
  85. from agency_swarm.ui.demos.copilot import CopilotDemoLauncher
  86. agency = Agency(Agent(name="CEO", instructions="test"), name="CopilotDemoAgency")
  87. expected = "http://localhost:8000/CopilotDemoAgency/get_response_stream"
  88. original_path_exists = Path.exists
  89. def fake_exists(self: Path) -> bool:
  90. if self.name == "node_modules":
  91. return True
  92. return original_path_exists(self)
  93. class _DummyProc:
  94. def terminate(self) -> None:
  95. return None
  96. with (
  97. patch("shutil.which", return_value="/usr/bin/npm"),
  98. patch.object(Path, "exists", fake_exists),
  99. patch("subprocess.Popen", return_value=_DummyProc()) as popen,
  100. patch("agency_swarm.ui.demos.copilot.run_fastapi", return_value=None),
  101. ):
  102. os.environ.pop("NEXT_PUBLIC_AG_UI_BACKEND_URL", None)
  103. CopilotDemoLauncher.start(agency, host="0.0.0.0", port=8000)
  104. assert os.environ["NEXT_PUBLIC_AG_UI_BACKEND_URL"] == expected
  105. _args, kwargs = popen.call_args
  106. assert "stdout" not in kwargs and "stderr" not in kwargs