utils.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. """
  2. Utility functions for MCPClient and Tiny Agents.
  3. Formatting utilities taken from the JS SDK: https://github.com/huggingface/huggingface.js/blob/main/packages/mcp-client/src/ResultFormatter.ts.
  4. """
  5. import json
  6. from pathlib import Path
  7. from typing import TYPE_CHECKING, Optional
  8. from huggingface_hub import snapshot_download
  9. from huggingface_hub.errors import EntryNotFoundError
  10. from .constants import DEFAULT_AGENT, DEFAULT_REPO_ID, FILENAME_CONFIG, PROMPT_FILENAMES
  11. from .types import AgentConfig
  12. if TYPE_CHECKING:
  13. from mcp import types as mcp_types
  14. def format_result(result: "mcp_types.CallToolResult") -> str:
  15. """
  16. Formats a mcp.types.CallToolResult content into a human-readable string.
  17. Args:
  18. result (CallToolResult)
  19. Object returned by mcp.ClientSession.call_tool.
  20. Returns:
  21. str
  22. A formatted string representing the content of the result.
  23. """
  24. content = result.content
  25. if len(content) == 0:
  26. return "[No content]"
  27. formatted_parts: list[str] = []
  28. for item in content:
  29. match item.type:
  30. case "text":
  31. formatted_parts.append(item.text)
  32. case "image":
  33. formatted_parts.append(
  34. f"[Binary Content: Image {item.mimeType}, {_get_base64_size(item.data)} bytes]\n"
  35. f"The task is complete and the content accessible to the User"
  36. )
  37. case "audio":
  38. formatted_parts.append(
  39. f"[Binary Content: Audio {item.mimeType}, {_get_base64_size(item.data)} bytes]\n"
  40. f"The task is complete and the content accessible to the User"
  41. )
  42. case "resource":
  43. resource = item.resource
  44. if hasattr(resource, "text") and isinstance(resource.text, str):
  45. formatted_parts.append(resource.text)
  46. elif hasattr(resource, "blob") and isinstance(resource.blob, str):
  47. formatted_parts.append(
  48. f"[Binary Content ({resource.uri}): {resource.mimeType},"
  49. f" {_get_base64_size(resource.blob)} bytes]\n"
  50. f"The task is complete and the content accessible to the User"
  51. )
  52. return "\n".join(formatted_parts)
  53. def _get_base64_size(base64_str: str) -> int:
  54. """Estimate the byte size of a base64-encoded string."""
  55. # Remove any prefix like "data:image/png;base64,"
  56. if "," in base64_str:
  57. base64_str = base64_str.split(",")[1]
  58. padding = 0
  59. if base64_str.endswith("=="):
  60. padding = 2
  61. elif base64_str.endswith("="):
  62. padding = 1
  63. return (len(base64_str) * 3) // 4 - padding
  64. def _load_agent_config(agent_path: Optional[str]) -> tuple[AgentConfig, Optional[str]]:
  65. """Load server config and prompt."""
  66. def _read_dir(directory: Path) -> tuple[AgentConfig, Optional[str]]:
  67. cfg_file = directory / FILENAME_CONFIG
  68. if not cfg_file.exists():
  69. raise FileNotFoundError(f" Config file not found in {directory}! Please make sure it exists locally")
  70. config: AgentConfig = json.loads(cfg_file.read_text(encoding="utf-8"))
  71. prompt: Optional[str] = None
  72. for filename in PROMPT_FILENAMES:
  73. prompt_file = directory / filename
  74. if prompt_file.exists():
  75. prompt = prompt_file.read_text(encoding="utf-8")
  76. break
  77. return config, prompt
  78. if agent_path is None:
  79. return DEFAULT_AGENT, None # type: ignore
  80. path = Path(agent_path).expanduser()
  81. if path.is_file():
  82. return json.loads(path.read_text(encoding="utf-8")), None
  83. if path.is_dir():
  84. return _read_dir(path)
  85. # fetch from the Hub
  86. try:
  87. repo_dir = Path(
  88. snapshot_download(
  89. repo_id=DEFAULT_REPO_ID,
  90. allow_patterns=f"{agent_path}/*",
  91. repo_type="dataset",
  92. )
  93. )
  94. return _read_dir(repo_dir / agent_path)
  95. except Exception as err:
  96. raise EntryNotFoundError(
  97. f" Agent {agent_path} not found in tiny-agents/tiny-agents! Please make sure it exists in https://huggingface.co/datasets/tiny-agents/tiny-agents."
  98. ) from err