Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
main agent search has better vibes
Browse files- agent/core/tools.py +44 -6
- agent/prompts/search_docs_system_prompt.yaml +0 -36
- agent/prompts/system_prompt.yaml +8 -3
- agent/tools/__init__.py +0 -3
- agent/tools/{_search_agent_tools.py → docs_tools.py} +3 -3
- agent/tools/jobs_tool.py +15 -16
- agent/tools/search_docs_tool.py +0 -272
- configs/_subagent_config_search_agent.json +0 -12
- run_search_agent.py +0 -162
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: {
|
| 193 |
)
|
| 194 |
# in order of importance
|
| 195 |
return [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
ToolSpec(
|
| 197 |
-
name=
|
| 198 |
-
description=
|
| 199 |
-
parameters=
|
| 200 |
-
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**:
|
| 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
|
|
|
|
| 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 |
-
-
|
|
|
|
| 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 |
-
|
| 3 |
-
|
| 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
|
| 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
|
| 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 |
-
|
| 70 |
-
|
| 71 |
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
result
|
| 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', '
|
| 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'
|
| 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 |
-
"
|
| 816 |
"type": "object",
|
| 817 |
-
"description": "Environment variables
|
| 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()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|