Aksel Joonas Reedi commited on
Commit
3765ba2
·
2 Parent(s): 1598bb4b307ff7

main agent search has better vibes

Browse files
agent/core/tools.py CHANGED
@@ -13,9 +13,14 @@ from lmnr import observe
13
  from mcp.types import EmbeddedResource, ImageContent, TextContent
14
 
15
  from agent.config import MCPServerConfig
 
 
 
 
 
 
16
  from agent.tools.jobs_tool import HF_JOBS_TOOL_SPEC, hf_jobs_handler
17
  from agent.tools.plan_tool import PLAN_TOOL_SPEC, plan_tool_handler
18
- from agent.tools.search_docs_tool import SEARCH_DOCS_TOOL_SPEC, search_docs_handler
19
 
20
  # Suppress aiohttp deprecation warning
21
  warnings.filterwarnings(
@@ -122,6 +127,27 @@ class ToolRouter:
122
  )
123
  )
124
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  def get_tool_specs_for_llm(self) -> list[dict[str, Any]]:
126
  """Get tool specifications in OpenAI format"""
127
  specs = []
@@ -145,6 +171,10 @@ class ToolRouter:
145
  await self.register_mcp_tools()
146
  self._mcp_initialized = True
147
  print(f"MCP initialized: {self._mcp_initialized}")
 
 
 
 
148
  return self
149
 
150
  async def __aexit__(self, exc_type, exc, tb) -> None:
@@ -189,16 +219,24 @@ class ToolRouter:
189
  def create_builtin_tools() -> list[ToolSpec]:
190
  """Create built-in tool specifications"""
191
  print(
192
- f"Creating built-in tools: {SEARCH_DOCS_TOOL_SPEC['name']}, {PLAN_TOOL_SPEC['name']}, {HF_JOBS_TOOL_SPEC['name']}"
193
  )
194
  # in order of importance
195
  return [
 
 
 
 
 
 
 
196
  ToolSpec(
197
- name=SEARCH_DOCS_TOOL_SPEC["name"],
198
- description=SEARCH_DOCS_TOOL_SPEC["description"],
199
- parameters=SEARCH_DOCS_TOOL_SPEC["parameters"],
200
- handler=search_docs_handler,
201
  ),
 
202
  ToolSpec(
203
  name=PLAN_TOOL_SPEC["name"],
204
  description=PLAN_TOOL_SPEC["description"],
 
13
  from mcp.types import EmbeddedResource, ImageContent, TextContent
14
 
15
  from agent.config import MCPServerConfig
16
+ from agent.tools.docs_tools import (
17
+ EXPLORE_HF_DOCS_TOOL_SPEC,
18
+ HF_DOCS_FETCH_TOOL_SPEC,
19
+ explore_hf_docs_handler,
20
+ hf_docs_fetch_handler,
21
+ )
22
  from agent.tools.jobs_tool import HF_JOBS_TOOL_SPEC, hf_jobs_handler
23
  from agent.tools.plan_tool import PLAN_TOOL_SPEC, plan_tool_handler
 
24
 
25
  # Suppress aiohttp deprecation warning
26
  warnings.filterwarnings(
 
127
  )
128
  )
129
 
130
+ async def register_openapi_tool(self) -> None:
131
+ """Register the OpenAPI search tool (requires async initialization)"""
132
+ from agent.tools.docs_tools import (
133
+ _get_api_search_tool_spec,
134
+ search_openapi_handler,
135
+ )
136
+
137
+ print("Registering OpenAPI search tool...")
138
+
139
+ # Register search_hf_api_endpoints with dynamic spec
140
+ openapi_spec = await _get_api_search_tool_spec()
141
+ self.register_tool(
142
+ ToolSpec(
143
+ name=openapi_spec["name"],
144
+ description=openapi_spec["description"],
145
+ parameters=openapi_spec["parameters"],
146
+ handler=search_openapi_handler,
147
+ )
148
+ )
149
+ print(f"Registered: {openapi_spec['name']}")
150
+
151
  def get_tool_specs_for_llm(self) -> list[dict[str, Any]]:
152
  """Get tool specifications in OpenAI format"""
153
  specs = []
 
171
  await self.register_mcp_tools()
172
  self._mcp_initialized = True
173
  print(f"MCP initialized: {self._mcp_initialized}")
174
+
175
+ # Register OpenAPI tool (requires async initialization)
176
+ await self.register_openapi_tool()
177
+
178
  return self
179
 
180
  async def __aexit__(self, exc_type, exc, tb) -> None:
 
219
  def create_builtin_tools() -> list[ToolSpec]:
220
  """Create built-in tool specifications"""
221
  print(
222
+ f"Creating built-in tools: {EXPLORE_HF_DOCS_TOOL_SPEC['name']}, {HF_DOCS_FETCH_TOOL_SPEC['name']}, {PLAN_TOOL_SPEC['name']}, {HF_JOBS_TOOL_SPEC['name']}"
223
  )
224
  # in order of importance
225
  return [
226
+ # Documentation search tools
227
+ ToolSpec(
228
+ name=EXPLORE_HF_DOCS_TOOL_SPEC["name"],
229
+ description=EXPLORE_HF_DOCS_TOOL_SPEC["description"],
230
+ parameters=EXPLORE_HF_DOCS_TOOL_SPEC["parameters"],
231
+ handler=explore_hf_docs_handler,
232
+ ),
233
  ToolSpec(
234
+ name=HF_DOCS_FETCH_TOOL_SPEC["name"],
235
+ description=HF_DOCS_FETCH_TOOL_SPEC["description"],
236
+ parameters=HF_DOCS_FETCH_TOOL_SPEC["parameters"],
237
+ handler=hf_docs_fetch_handler,
238
  ),
239
+ # Planning and job management tools
240
  ToolSpec(
241
  name=PLAN_TOOL_SPEC["name"],
242
  description=PLAN_TOOL_SPEC["description"],
agent/prompts/search_docs_system_prompt.yaml DELETED
@@ -1,36 +0,0 @@
1
- search_docs_system_prompt: |
2
- You are a specialized documentation search agent. Your task is to comprehensively search and synthesize information from Hugging Face documentation. You are queried by a main agent who has to build a solution to a user. You have to give the best and the most comprehensive guidance on how to solve the user's task.
3
-
4
- # Search Strategy
5
-
6
- You must search thoroughly before synthesizing results. Follow this approach:
7
-
8
- 1. **Query Analysis**: Identify the core concepts and intent of the original user query and the search query passed by the LLM.
9
- 2. **Initial Search**: Start with a broad search capturing the main topic
10
- 3. **Iterative Refinement**: Run multiple searches to go deeper into topics. If you see links to other pages, also look into those pages for best information - first-pass results often miss key details
11
- 4. **You must get to the end truth**: You must get to the bottom of the truth for this search query. You CAN NOT say that somebody should look up documentation. You must look it up yourself and give the best answer you can including code snippets and relevant information. You are teaching the main agent how to solve the user's task and have to give ALL relevant information on how to do it.
12
-
13
- # Quality metrics:
14
- - You are optimizing for the minimum viable way to solve the user request reusing as much as possible from already available code from your research. Opt for reliability and reusability. Hugging Face has a lot of best practices laid out in the documentation and you must pass these to the main agent.
15
-
16
-
17
- # Useful links:
18
- - code examples for trl (covers most LLM training tasks): https://github.com/huggingface/trl/tree/main/examples/scripts and https://github.com/huggingface/trl/tree/main/trl/scripts
19
-
20
- # Response Guidelines
21
-
22
- After gathering results, synthesize them following these principles:
23
-
24
- 1. **Analyze Relevance**: Evaluate which results directly answer the query
25
- 2. **Synthesize**: Combine information from multiple sources when applicable
26
- 3. **Prioritize**: Present information in order of relevance
27
- 4. **Cite Sources**: Find and pass the relevant code and other snippets from the analyzed articles for the main agent to read.
28
- 5. **Acknowledge Gaps**: If documents don't fully answer the query, explicitly state this
29
- 6. **Handle Conflicts**: If sources contradict, note this and explain your reasoning
30
-
31
- # Constraints
32
-
33
- - Only provide information found in the documentation
34
- - Do not make assumptions beyond what the sources state
35
- - If information is not found, say so clearly rather than guessing
36
- - Focus on giving the best practices and comprehensive guidance on how to solve the user's task. Include all relevant code snippets without edits from the docs and simplest ways on how to solve the user's task.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
agent/prompts/system_prompt.yaml CHANGED
@@ -6,8 +6,11 @@ system_prompt: |
6
  **CRITICAL: Research First, Then Implement**
7
 
8
  For ANY implementation task (training, fine-tuning, inference, data processing, etc.):
9
- 1. **FIRST**: Use `research_solution` to search HF documentation and find the recommended approach
10
  - This is MANDATORY before writing any code or making implementation decisions
 
 
 
11
  - Research what libraries to use, find code examples, understand best practices
12
  - Skip ONLY for simple factual questions (e.g., "What is LoRA?")
13
 
@@ -41,13 +44,15 @@ system_prompt: |
41
 
42
  # Conventions
43
 
44
- - **ALWAYS use `research_solution` BEFORE implementing** any ML workflow (training, inference, data processing, etc.) - This is non-negotiable
 
45
  - Never assume you know the correct library, method, or approach - you must verify with documentation first
46
  - Base your implementation on researched best practices, not general knowledge or assumptions
47
  - Always search Hugging Face Hub for existing resources before suggesting custom implementations
48
  - Keep in mind that a space is a repo, so you can create a space directly by uploading files that way. Repos should also be used to store files permanently : post-execution, files from jobs are not available.
49
  - To run jobs, you must always pass the whole content of the file to execute. No files are available on server. Your local files and distant files are entirely seperate scopes.
50
- - To access, create, or modify private Hub assets (spaces, private models, datasets, collections), pass `secrets: {% raw %}{{ "HF_TOKEN": "$HF_TOKEN" }}{% endraw %}` along with the jobs parameters. This is important. Without it, you will encounter authentification issues. Do not assume the user is connected on the jobs' server.
 
51
  - When referencing models, datasets, or papers, include direct links from search results
52
  - Before processing any dataset: inspect its actual structure first using the mcp__hf-mcp-server__hub_repo_details tool. Never assume column names: verify them beforehand.
53
  - Follow ML best practices: proper train/val/test splits, reproducibility, evaluation metrics
 
6
  **CRITICAL: Research First, Then Implement**
7
 
8
  For ANY implementation task (training, fine-tuning, inference, data processing, etc.):
9
+ 1. **FIRST**: Search HF documentation to find the recommended approach
10
  - This is MANDATORY before writing any code or making implementation decisions
11
+ - Use `explore_hf_docs` to discover documentation structure for relevant libraries (e.g., "trl", "transformers", "diffusers")
12
+ - Use `fetch_hf_docs` to retrieve full content from specific documentation pages
13
+ - Use `search_hf_api_endpoints` to find API endpoints with usage examples
14
  - Research what libraries to use, find code examples, understand best practices
15
  - Skip ONLY for simple factual questions (e.g., "What is LoRA?")
16
 
 
44
 
45
  # Conventions
46
 
47
+ - **ALWAYS search documentation BEFORE implementing** any ML workflow (training, inference, data processing, etc.) - This is non-negotiable
48
+ - Use `explore_hf_docs`, `fetch_hf_docs`, and `search_hf_api_endpoints` to research the correct approach
49
  - Never assume you know the correct library, method, or approach - you must verify with documentation first
50
  - Base your implementation on researched best practices, not general knowledge or assumptions
51
  - Always search Hugging Face Hub for existing resources before suggesting custom implementations
52
  - Keep in mind that a space is a repo, so you can create a space directly by uploading files that way. Repos should also be used to store files permanently : post-execution, files from jobs are not available.
53
  - To run jobs, you must always pass the whole content of the file to execute. No files are available on server. Your local files and distant files are entirely seperate scopes.
54
+ - The HF_TOKEN is automatically loaded from the environment variables.
55
+ -
56
  - When referencing models, datasets, or papers, include direct links from search results
57
  - Before processing any dataset: inspect its actual structure first using the mcp__hf-mcp-server__hub_repo_details tool. Never assume column names: verify them beforehand.
58
  - Follow ML best practices: proper train/val/test splits, reproducibility, evaluation metrics
agent/tools/__init__.py CHANGED
@@ -3,7 +3,6 @@ Hugging Face tools for the agent
3
  """
4
 
5
  from agent.tools.jobs_tool import HF_JOBS_TOOL_SPEC, HfJobsTool, hf_jobs_handler
6
- from agent.tools.search_docs_tool import SEARCH_DOCS_TOOL_SPEC, search_docs_handler
7
  from agent.tools.types import ToolResult
8
 
9
  __all__ = [
@@ -11,6 +10,4 @@ __all__ = [
11
  "HF_JOBS_TOOL_SPEC",
12
  "hf_jobs_handler",
13
  "HfJobsTool",
14
- "SEARCH_DOCS_TOOL_SPEC",
15
- "search_docs_handler",
16
  ]
 
3
  """
4
 
5
  from agent.tools.jobs_tool import HF_JOBS_TOOL_SPEC, HfJobsTool, hf_jobs_handler
 
6
  from agent.tools.types import ToolResult
7
 
8
  __all__ = [
 
10
  "HF_JOBS_TOOL_SPEC",
11
  "hf_jobs_handler",
12
  "HfJobsTool",
 
 
13
  ]
agent/tools/{_search_agent_tools.py → docs_tools.py} RENAMED
@@ -1,6 +1,6 @@
1
  """
2
- Tools available to the search sub-agent
3
- These tools are used by the search sub-agent spawned by search_docs_tool
4
  """
5
 
6
  import asyncio
@@ -553,7 +553,7 @@ async def hf_docs_fetch_handler(arguments: dict[str, Any]) -> tuple[str, bool]:
553
  return f"Error fetching documentation: {str(e)}", False
554
 
555
 
556
- # Tool specifications for the search sub-agent
557
 
558
  EXPLORE_HF_DOCS_TOOL_SPEC = {
559
  "name": "explore_hf_docs",
 
1
  """
2
+ Documentation search tools for the HF Agent
3
+ Tools for exploring and fetching HuggingFace documentation and API specifications
4
  """
5
 
6
  import asyncio
 
553
  return f"Error fetching documentation: {str(e)}", False
554
 
555
 
556
+ # Tool specifications for documentation search
557
 
558
  EXPLORE_HF_DOCS_TOOL_SPEC = {
559
  "name": "explore_hf_docs",
agent/tools/jobs_tool.py CHANGED
@@ -10,7 +10,7 @@ import os
10
  from typing import Any, Dict, Literal, Optional
11
 
12
  from huggingface_hub import HfApi
13
- from huggingface_hub.utils import HfHubHTTPError, get_token_to_send
14
 
15
  from agent.tools.types import ToolResult
16
  from agent.tools.utilities import (
@@ -63,20 +63,19 @@ UV_DEFAULT_IMAGE = "ghcr.io/astral-sh/uv:python3.12-bookworm"
63
 
64
 
65
  def _add_environment_variables(params: Dict[str, Any] | None) -> Dict[str, Any]:
66
- """
67
- Automatically adds selected environment variables to the parameters passed by LLM.
68
 
69
- Args:
70
- params: Dictionary that may contain "HF_TOKEN" and other environment variables as keys
71
 
72
- Returns:
73
- Dictionary with environment variables added
74
- """
75
 
76
- result = {"HF_TOKEN": get_token_to_send(os.environ.get("HF_TOKEN", ""))}
77
-
78
- if params:
79
- result.update(params)
80
 
81
  return result
82
 
@@ -747,9 +746,9 @@ HF_JOBS_TOOL_SPEC = {
747
  "GPU: t4-small, t4-medium, l4x1, a10g-small, a10g-large, a100-large, h100\n\n"
748
  "## Examples:\n\n"
749
  "**Fine-tune LLM and push to Hub:**\n"
750
- "{'operation': 'run', 'script': 'from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer\\nmodel = AutoModelForCausalLM.from_pretrained(\"gpt2\")\\n# ... training code ...\\nmodel.push_to_hub(\"my-finetuned-model\")', 'dependencies': ['transformers', 'torch', 'datasets'], 'hardware_flavor': 'a10g-large', 'timeout': '4h', 'secrets': {'HF_TOKEN': '$HF_TOKEN'}}\n\n"
751
  "**Generate dataset daily and upload:**\n"
752
- "{'operation': 'scheduled run', 'script': 'from datasets import Dataset\\nimport pandas as pd\\n# scrape/generate data\\ndf = pd.DataFrame(data)\\nds = Dataset.from_pandas(df)\\nds.push_to_hub(\"daily-dataset\")', 'dependencies': ['datasets', 'pandas'], 'schedule': '@daily', 'secrets': {'HF_TOKEN': '$HF_TOKEN'}}\n\n"
753
  "**Run custom training with Docker:**\n"
754
  "{'operation': 'run', 'image': 'pytorch/pytorch:2.0.0-cuda11.7-cudnn8-runtime', 'command': ['python', 'train.py', '--epochs', '10'], 'hardware_flavor': 'a100-large'}\n\n"
755
  "**Monitor jobs:**\n"
@@ -812,9 +811,9 @@ HF_JOBS_TOOL_SPEC = {
812
  "type": "string",
813
  "description": "Max runtime. Examples: '30m', '2h', '4h'. Default: '30m'. Important for long training jobs. Use with 'run'/'scheduled run'.",
814
  },
815
- "secrets": {
816
  "type": "object",
817
- "description": "Environment variables (private). Format: {'KEY': 'VALUE'}. Use {'HF_TOKEN': '$HF_TOKEN'} for Hub auth. Use with 'run'/'scheduled run'.",
818
  },
819
  # Job management parameters
820
  "job_id": {
 
10
  from typing import Any, Dict, Literal, Optional
11
 
12
  from huggingface_hub import HfApi
13
+ from huggingface_hub.utils import HfHubHTTPError
14
 
15
  from agent.tools.types import ToolResult
16
  from agent.tools.utilities import (
 
63
 
64
 
65
  def _add_environment_variables(params: Dict[str, Any] | None) -> Dict[str, Any]:
66
+ token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACE_HUB_TOKEN") or ""
 
67
 
68
+ # Start with user-provided env vars, then force-set token last
69
+ result = dict(params or {})
70
 
71
+ # If the caller passed HF_TOKEN="$HF_TOKEN", ignore it.
72
+ if result.get("HF_TOKEN", "").strip().startswith("$"):
73
+ result.pop("HF_TOKEN", None)
74
 
75
+ # Set both names to be safe (different libs check different vars)
76
+ if token:
77
+ result["HF_TOKEN"] = token
78
+ result["HUGGINGFACE_HUB_TOKEN"] = token
79
 
80
  return result
81
 
 
746
  "GPU: t4-small, t4-medium, l4x1, a10g-small, a10g-large, a100-large, h100\n\n"
747
  "## Examples:\n\n"
748
  "**Fine-tune LLM and push to Hub:**\n"
749
+ "{'operation': 'run', 'script': 'from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer\\nmodel = AutoModelForCausalLM.from_pretrained(\"gpt2\")\\n# ... training code ...\\nmodel.push_to_hub(\"user-name/my-finetuned-model\")', 'dependencies': ['transformers', 'torch', 'datasets'], 'hardware_flavor': 'a10g-large', 'timeout': '4h', 'env': {'CUSTOM_VAR': 'value'}}\n\n"
750
  "**Generate dataset daily and upload:**\n"
751
+ "{'operation': 'scheduled run', 'script': 'from datasets import Dataset\\nimport pandas as pd\\n# scrape/generate data\\ndf = pd.DataFrame(data)\\nds = Dataset.from_pandas(df)\\nds.push_to_hub(\"user-name/daily-dataset\")', 'dependencies': ['datasets', 'pandas'], 'schedule': '@daily'}\n\n"
752
  "**Run custom training with Docker:**\n"
753
  "{'operation': 'run', 'image': 'pytorch/pytorch:2.0.0-cuda11.7-cudnn8-runtime', 'command': ['python', 'train.py', '--epochs', '10'], 'hardware_flavor': 'a100-large'}\n\n"
754
  "**Monitor jobs:**\n"
 
811
  "type": "string",
812
  "description": "Max runtime. Examples: '30m', '2h', '4h'. Default: '30m'. Important for long training jobs. Use with 'run'/'scheduled run'.",
813
  },
814
+ "env": {
815
  "type": "object",
816
+ "description": "Environment variables. Format: {'KEY': 'VALUE'}. HF_TOKEN is automatically included from your auth. Use with 'run'/'scheduled run'.",
817
  },
818
  # Job management parameters
819
  "job_id": {
agent/tools/search_docs_tool.py DELETED
@@ -1,272 +0,0 @@
1
- """
2
- Search documentation tool that spawns a sub-agent
3
- The sub-agent has its own agent loop and set of specialized search tools
4
- """
5
-
6
- import asyncio
7
- from typing import Any
8
-
9
- from litellm.utils import get_max_tokens
10
-
11
- from agent.core.session import Session
12
-
13
-
14
- async def create_search_tool_router(github_mcp_config: dict[str, Any] | None = None):
15
- """
16
- Create a ToolRouter instance for the search sub-agent
17
- Async because OpenAPI tool needs to fetch and parse spec at initialization
18
-
19
- Args:
20
- github_mcp_config: Optional GitHub MCP server configuration
21
- """
22
- # Import at runtime to avoid circular dependency
23
- from fastmcp import Client
24
-
25
- from agent.core.tools import ToolRouter
26
-
27
- # List of allowed GitHub MCP tools
28
- ALLOWED_GITHUB_TOOLS = {
29
- "list_pull_requests",
30
- "list_issues",
31
- "search_code",
32
- "search_issues",
33
- "search_repositories",
34
- "search_users",
35
- "get_pull_request_status",
36
- "get_pull_request_reviews",
37
- "get_pull_request",
38
- "get_issue",
39
- "get_file_contents",
40
- }
41
-
42
- class SearchDocsToolRouter(ToolRouter):
43
- """Specialized ToolRouter for the search sub-agent"""
44
-
45
- def __init__(self, github_mcp_config: dict[str, Any] | None = None):
46
- self.tools: dict[str, Any] = {}
47
- self.mcp_servers: dict[str, dict[str, Any]] = {}
48
- self._mcp_initialized = False
49
-
50
- # Initialize MCP client with GitHub server if provided
51
- if github_mcp_config:
52
- self.mcp_client = Client({"mcpServers": github_mcp_config})
53
- else:
54
- self.mcp_client = None
55
-
56
- async def initialize_tools(self):
57
- """Initialize tools asynchronously"""
58
- tools = await make_search_agent_tools()
59
- for tool in tools:
60
- self.register_tool(tool)
61
-
62
- async def register_mcp_tools(self) -> None:
63
- """Register only allowed GitHub MCP tools"""
64
- if self.mcp_client is None:
65
- return
66
-
67
- tools = await self.mcp_client.list_tools()
68
- for tool in tools:
69
- # Only register allowed GitHub tools
70
- if tool.name in ALLOWED_GITHUB_TOOLS:
71
- print(f"Registering GitHub MCP Tool: {tool.name}")
72
- from agent.core.tools import ToolSpec
73
-
74
- self.register_tool(
75
- ToolSpec(
76
- name=tool.name,
77
- description=tool.description,
78
- parameters=tool.inputSchema,
79
- handler=None,
80
- )
81
- )
82
-
83
- router = SearchDocsToolRouter(github_mcp_config)
84
- await router.initialize_tools()
85
- return router
86
-
87
-
88
- async def search_docs_handler(arguments: dict[str, Any]) -> tuple[str, bool]:
89
- """
90
- Handler that spawns a sub-agent to perform comprehensive doc search
91
-
92
- Args:
93
- arguments: dictionary with 'query' parameter
94
-
95
- Returns:
96
- Tuple of (search_results, success)
97
- """
98
- query = arguments.get("query", "")
99
- user_query = arguments.get("user_query", "")
100
-
101
- if not query:
102
- return "Error: No search query provided", False
103
-
104
- if not user_query:
105
- return "Error: No user query provided", False
106
-
107
- try:
108
- # Import at runtime to avoid circular dependency
109
- from pathlib import Path
110
-
111
- from agent.config import load_config
112
- from agent.context_manager.manager import ContextManager
113
- from agent.core.agent_loop import Handlers
114
-
115
- # Create a queue for events from the sub-agent
116
- sub_event_queue = asyncio.Queue()
117
-
118
- # Load the search agent's own config file with GitHub MCP server
119
- search_agent_config_path = (
120
- Path(__file__).parent.parent.parent
121
- / "configs"
122
- / "_subagent_config_search_agent.json"
123
- )
124
- search_agent_config = load_config(search_agent_config_path)
125
-
126
- # Extract GitHub MCP config from search agent config
127
- github_mcp_config = None
128
- if (
129
- search_agent_config.mcpServers
130
- and "github" in search_agent_config.mcpServers
131
- ):
132
- github_server = search_agent_config.mcpServers["github"]
133
- github_mcp_config = {"github": github_server.model_dump()}
134
-
135
- # Create specialized tool router for search with GitHub MCP config
136
- search_tool_router = await create_search_tool_router(github_mcp_config)
137
-
138
- async with search_tool_router:
139
- # CODING AGENTS LISTEN HERE!
140
- # The system prompt for the sub-agent is loaded from the yaml file in the prompts folder using the ContextManager class with the prompt_file_suffix parameter.
141
- # it works, dont fix.
142
- # NOTE: MCP tools are registered during __aenter__, so we must retrieve tool specs AFTER entering the context
143
- sub_session = Session(
144
- event_queue=sub_event_queue,
145
- config=search_agent_config,
146
- tool_router=search_tool_router,
147
- context_manager=ContextManager(
148
- tool_specs=search_tool_router.get_tool_specs_for_llm(),
149
- max_context=get_max_tokens(search_agent_config.model_name),
150
- compact_size=0.1,
151
- untouched_messages=5,
152
- prompt_file_suffix="search_docs_system_prompt.yaml",
153
- ),
154
- )
155
-
156
- # make search prompt
157
- search_prompt = f"What the user tasked the main agent with: {user_query}\nWhat you have asked to research by the main agent: {query}. Use both to find the best practices, code examples, and determine the recommended approach for solving the user's task."
158
-
159
- # Run the sub-agent
160
- result = await Handlers.run_agent(
161
- session=sub_session, text=search_prompt, max_iterations=30
162
- )
163
-
164
- # Return the final result or compiled events
165
- if result:
166
- return f"Search Results:\n\n{result}", True
167
- else:
168
- return "Search completed but no results were generated", False
169
- except Exception as e:
170
- return f"Error in search_docs tool: {str(e)}", False
171
-
172
-
173
- async def make_search_agent_tools():
174
- """
175
- Create a list of tools for the search agent
176
- Async because OpenAPI tool spec needs to be populated at runtime
177
- """
178
- # Import at runtime to avoid circular dependency
179
- from agent.core.tools import ToolSpec
180
- from agent.tools._search_agent_tools import (
181
- EXPLORE_HF_DOCS_TOOL_SPEC,
182
- HF_DOCS_FETCH_TOOL_SPEC,
183
- _get_api_search_tool_spec,
184
- explore_hf_docs_handler,
185
- hf_docs_fetch_handler,
186
- search_openapi_handler,
187
- )
188
-
189
- # Get the OpenAPI tool spec with dynamically populated tags
190
- openapi_spec = await _get_api_search_tool_spec()
191
-
192
- return [
193
- ToolSpec(
194
- name=EXPLORE_HF_DOCS_TOOL_SPEC["name"],
195
- description=EXPLORE_HF_DOCS_TOOL_SPEC["description"],
196
- parameters=EXPLORE_HF_DOCS_TOOL_SPEC["parameters"],
197
- handler=explore_hf_docs_handler,
198
- ),
199
- ToolSpec(
200
- name=HF_DOCS_FETCH_TOOL_SPEC["name"],
201
- description=HF_DOCS_FETCH_TOOL_SPEC["description"],
202
- parameters=HF_DOCS_FETCH_TOOL_SPEC["parameters"],
203
- handler=hf_docs_fetch_handler,
204
- ),
205
- ToolSpec(
206
- name=openapi_spec["name"],
207
- description=openapi_spec["description"],
208
- parameters=openapi_spec["parameters"],
209
- handler=search_openapi_handler,
210
- ),
211
- ]
212
-
213
-
214
- # Tool specification to be used by the main agent
215
- SEARCH_DOCS_TOOL_SPEC = {
216
- "name": "research_solution",
217
- "description": (
218
- "Spawns a specialized research sub-agent to search to find best practices, locate code examples, and determine the recommended approach for solving the user's task.\n\n"
219
- "SEARCH AGENT CAPABILITIES:\n"
220
- "The search subagent has access to these specialized tools:\n"
221
- " - explore_hf_docs: Discovers documentation structure by parsing sidebar navigation, returns page titles, URLs, and content glimpses\n"
222
- " - fetch_hf_docs: Retrieves full markdown content from specific HF documentation pages\n"
223
- " - search_hf_api_endpoints: Searches HF OpenAPI specification by tag to find API endpoints with usage examples\n"
224
- " - GitHub tools: search_code, search_repositories, get_file_contents, list_issues, list_pull_requests (for searching HF repositories)\n"
225
- "MANDATORY FIRST STEP for:\n"
226
- " - ANY task involving training, fine-tuning, or model deployment with HF libraries\n"
227
- " - Implementing ML workflows (data loading, preprocessing, training loops, inference pipelines)\n"
228
- " - Working with specific HF libraries (transformers, diffusers, trl, datasets, accelerate, etc.)\n"
229
- " - Finding the recommended/official way to accomplish ML tasks\n"
230
- " - Understanding which libraries and methods to use for a user's goal\n\n"
231
- "ALSO USE for:\n"
232
- " - Verifying current API signatures, parameters, or available methods\n"
233
- " - Finding code examples and best practices from official documentation\n"
234
- " - Understanding relationships between HF libraries and components\n\n"
235
- "SKIP ONLY when:\n"
236
- " - User asks simple factual questions answerable from general ML knowledge (e.g., 'What is fine-tuning?')\n"
237
- " - Task is about general Python/programming unrelated to ML or HF libraries\n"
238
- "QUERY FORMAT:\n"
239
- "Write queries as if delegating to an engineer. Include:\n"
240
- " - Specific library names (e.g., 'trl', 'transformers', 'diffusers')\n"
241
- " - Technical terminology from the domain (e.g., 'DPO trainer', 'GRPO', 'LoRA adapter')\n"
242
- " - Clear success criteria (e.g., 'find code example', 'verify parameter exists', 'get recommended approach')\n\n"
243
- "QUERY EXAMPLES:\n"
244
- " Good: 'Find the best way to implement DPO training in trl. Get code example showing dataset format, trainer configuration, and reward model setup'\n"
245
- " Bad: 'dpo trainer'\n"
246
- " Good: 'Search transformers docs for the recommended approach to load and run quantized models with 4-bit precision. Find the specific classes and methods to use'\n"
247
- " Bad: 'quantization'\n"
248
- " Good: 'Research the best way to fine-tune a diffusion model for custom image generation. Find which library to use (diffusers/PEFT), required components, and complete training example'\n"
249
- " Bad: 'fine-tune diffusion'\n\n"
250
- ),
251
- "parameters": {
252
- "type": "object",
253
- "properties": {
254
- "user_query": {
255
- "type": "string",
256
- "description": (
257
- "The original user query that you received. This will be used to search the documentation."
258
- ),
259
- },
260
- "query": {
261
- "type": "string",
262
- "description": (
263
- "Detailed search query for the specialized agent. Must include: (1) specific library/component names, "
264
- "(2) technical terms or concepts to search for, (3) clear objective (e.g., 'find code example', "
265
- "'verify API exists', 'get implementation details'). The search agent will autonomously explore "
266
- "documentation structure, retrieve relevant pages, and compile results until the objective is met."
267
- ),
268
- },
269
- },
270
- "required": ["user_query", "query"],
271
- },
272
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
configs/_subagent_config_search_agent.json DELETED
@@ -1,12 +0,0 @@
1
- {
2
- "model_name": "anthropic/claude-haiku-4-5",
3
- "mcpServers": {
4
- "github": {
5
- "transport": "http",
6
- "url": "https://api.githubcopilot.com/mcp/",
7
- "headers": {
8
- "Authorization": "Bearer ${GITHUB_TOKEN}"
9
- }
10
- }
11
- }
12
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
run_search_agent.py DELETED
@@ -1,162 +0,0 @@
1
- """
2
- Standalone test script for the search sub-agent
3
- Run with: uv run python test_search_agent.py
4
- """
5
-
6
- import asyncio
7
-
8
- from litellm.utils import get_max_tokens
9
-
10
- from agent.config import Config
11
- from agent.context_manager.manager import ContextManager
12
- from agent.core.agent_loop import Handlers
13
- from agent.core.session import Session
14
- from agent.tools.search_docs_tool import create_search_tool_router
15
-
16
-
17
- async def test_search_agent(query: str):
18
- """Test the search sub-agent with a query"""
19
- print(f"Testing search agent with query: {query}\n")
20
- print("=" * 60)
21
-
22
- # Import at runtime
23
- from pathlib import Path
24
-
25
- from agent.config import load_config
26
-
27
- # Create event queue for the sub-agent
28
- sub_event_queue = asyncio.Queue()
29
-
30
- # Load the search agent's own config file with GitHub MCP server
31
- search_agent_config_path = (
32
- Path(__file__).parent / "configs" / "_subagent_config_search_agent.json"
33
- )
34
- search_agent_config = load_config(search_agent_config_path)
35
-
36
- # Extract GitHub MCP config from search agent config
37
- github_mcp_config = None
38
- if search_agent_config.mcpServers and "github" in search_agent_config.mcpServers:
39
- github_server = search_agent_config.mcpServers["github"]
40
- github_mcp_config = {"github": github_server.model_dump()}
41
-
42
- # Create search tool router with GitHub MCP config
43
- search_tool_router = await create_search_tool_router(github_mcp_config)
44
-
45
- # Create config
46
- sub_config = Config(
47
- model_name="anthropic/claude-haiku-4-5",
48
- )
49
-
50
- # Event listener to show what the sub-agent is doing
51
- async def event_monitor():
52
- while True:
53
- try:
54
- event = await asyncio.wait_for(sub_event_queue.get(), timeout=1.0)
55
-
56
- if event.event_type == "assistant_message":
57
- content = event.data.get("content", "") if event.data else ""
58
- if content:
59
- print(f"\n🤖 Sub-agent: {content}\n")
60
-
61
- elif event.event_type == "tool_call":
62
- tool_name = event.data.get("tool", "") if event.data else ""
63
- arguments = event.data.get("arguments", {}) if event.data else {}
64
- print(f"🔧 Tool call: {tool_name}")
65
- print(f" Args: {arguments}")
66
-
67
- elif event.event_type == "tool_output":
68
- output = event.data.get("output", "") if event.data else ""
69
- success = event.data.get("success", False) if event.data else False
70
- status = "✅" if success else "❌"
71
-
72
- print(f"{status} Tool output: {output}\n")
73
-
74
- elif event.event_type == "turn_complete":
75
- print("✅ Sub-agent turn complete")
76
- break
77
-
78
- except asyncio.TimeoutError:
79
- # Check if agent is still running
80
- continue
81
- except Exception as e:
82
- print(f"⚠️ Event error: {e}")
83
- break
84
-
85
- # Run the sub-agent and event monitor concurrently
86
- async with search_tool_router:
87
- # Create session with custom system prompt
88
- # NOTE: MCP tools are registered during __aenter__, so we must create session AFTER entering the context
89
- sub_session = Session(
90
- event_queue=sub_event_queue,
91
- config=sub_config,
92
- tool_router=search_tool_router,
93
- context_manager=ContextManager(
94
- tool_specs=search_tool_router.get_tool_specs_for_llm(),
95
- max_context=get_max_tokens(sub_config.model_name),
96
- compact_size=0.1,
97
- untouched_messages=5,
98
- prompt_file_suffix="search_docs_system_prompt.yaml",
99
- ),
100
- )
101
-
102
- monitor_task = asyncio.create_task(event_monitor())
103
-
104
- result = await Handlers.run_agent(
105
- session=sub_session, text=query, max_iterations=30
106
- )
107
-
108
- # Wait for event monitor to finish
109
- await asyncio.wait_for(monitor_task, timeout=5.0)
110
-
111
- print("\n" + "=" * 60)
112
- print("FINAL RESULT:")
113
- print("=" * 60)
114
- if result:
115
- print(result)
116
- else:
117
- print("No result returned")
118
- print("=" * 60)
119
-
120
-
121
- async def main():
122
- """Main test function"""
123
- print("🧪 Search Sub-Agent Test\n")
124
-
125
- # Example queries to test
126
- test_queries = [
127
- # "Explore the TRL documentation structure and find information about DPO trainer",
128
- # "is there a way to get the logs from a served huggingface space",
129
- """use exactly this call {\"tool_name\": \"search_hf_docs\", \"arguments\": {\"query\": \"vLLM offline batch inference Hugging Face models\"}}""",
130
- # "How do I train GLM4.7 with a GRPO training loop with trl with llm judge as a reward model for training on hle?"
131
- # "can i stream logs through the api for a served huggingface space",
132
- # 'what tools do you have access to?',
133
- ]
134
-
135
- for i, query in enumerate(test_queries, 1):
136
- print(f"\n{'=' * 60}")
137
- print(f"TEST {i}/{len(test_queries)}")
138
- print(f"{'=' * 60}\n")
139
-
140
- try:
141
- await test_search_agent(query)
142
- except Exception as e:
143
- print(f"\n❌ Test failed: {e}")
144
- import traceback
145
-
146
- traceback.print_exc()
147
-
148
- if i < len(test_queries):
149
- print("\n\nPress Enter to continue to next test...")
150
- input()
151
-
152
-
153
- if __name__ == "__main__":
154
- try:
155
- asyncio.run(main())
156
- except KeyboardInterrupt:
157
- print("\n\n⚠️ Test interrupted")
158
- except Exception as e:
159
- print(f"\n❌ Error: {e}")
160
- import traceback
161
-
162
- traceback.print_exc()