Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Henri Bonamy commited on
Commit ·
226dea1
1
Parent(s): b1d24de
log streaming
Browse files- agent/core/agent_loop.py +4 -2
- agent/core/tools.py +7 -1
- agent/tools/jobs_tool.py +25 -4
- frontend/src/hooks/useAgentWebSocket.ts +33 -0
- frontend/src/types/events.ts +6 -0
agent/core/agent_loop.py
CHANGED
|
@@ -203,7 +203,7 @@ class Handlers:
|
|
| 203 |
)
|
| 204 |
|
| 205 |
output, success = await session.tool_router.call_tool(
|
| 206 |
-
tool_name, tool_args
|
| 207 |
)
|
| 208 |
|
| 209 |
# Add tool result to history
|
|
@@ -376,7 +376,9 @@ class Handlers:
|
|
| 376 |
)
|
| 377 |
)
|
| 378 |
|
| 379 |
-
output, success = await session.tool_router.call_tool(
|
|
|
|
|
|
|
| 380 |
|
| 381 |
return (tc, tool_name, output, success)
|
| 382 |
|
|
|
|
| 203 |
)
|
| 204 |
|
| 205 |
output, success = await session.tool_router.call_tool(
|
| 206 |
+
tool_name, tool_args, session=session
|
| 207 |
)
|
| 208 |
|
| 209 |
# Add tool result to history
|
|
|
|
| 376 |
)
|
| 377 |
)
|
| 378 |
|
| 379 |
+
output, success = await session.tool_router.call_tool(
|
| 380 |
+
tool_name, tool_args, session=session
|
| 381 |
+
)
|
| 382 |
|
| 383 |
return (tc, tool_name, output, success)
|
| 384 |
|
agent/core/tools.py
CHANGED
|
@@ -219,7 +219,7 @@ class ToolRouter:
|
|
| 219 |
|
| 220 |
@observe(name="call_tool")
|
| 221 |
async def call_tool(
|
| 222 |
-
self, tool_name: str, arguments: dict[str, Any]
|
| 223 |
) -> tuple[str, bool]:
|
| 224 |
"""
|
| 225 |
Call a tool and return (output_string, success_bool).
|
|
@@ -230,6 +230,12 @@ class ToolRouter:
|
|
| 230 |
# Check if this is a built-in tool with a handler
|
| 231 |
tool = self.tools.get(tool_name)
|
| 232 |
if tool and tool.handler:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
return await tool.handler(arguments)
|
| 234 |
|
| 235 |
# Otherwise, use MCP client
|
|
|
|
| 219 |
|
| 220 |
@observe(name="call_tool")
|
| 221 |
async def call_tool(
|
| 222 |
+
self, tool_name: str, arguments: dict[str, Any], session: Any = None
|
| 223 |
) -> tuple[str, bool]:
|
| 224 |
"""
|
| 225 |
Call a tool and return (output_string, success_bool).
|
|
|
|
| 230 |
# Check if this is a built-in tool with a handler
|
| 231 |
tool = self.tools.get(tool_name)
|
| 232 |
if tool and tool.handler:
|
| 233 |
+
import inspect
|
| 234 |
+
|
| 235 |
+
# Check if handler accepts session argument
|
| 236 |
+
sig = inspect.signature(tool.handler)
|
| 237 |
+
if "session" in sig.parameters:
|
| 238 |
+
return await tool.handler(arguments, session=session)
|
| 239 |
return await tool.handler(arguments)
|
| 240 |
|
| 241 |
# Otherwise, use MCP client
|
agent/tools/jobs_tool.py
CHANGED
|
@@ -9,12 +9,13 @@ import base64
|
|
| 9 |
import http.client
|
| 10 |
import os
|
| 11 |
import re
|
| 12 |
-
from typing import Any, Dict, Literal, Optional
|
| 13 |
|
| 14 |
import httpx
|
| 15 |
from huggingface_hub import HfApi
|
| 16 |
from huggingface_hub.utils import HfHubHTTPError
|
| 17 |
|
|
|
|
| 18 |
from agent.tools.types import ToolResult
|
| 19 |
from agent.tools.utilities import (
|
| 20 |
format_job_details,
|
|
@@ -269,9 +270,15 @@ def _scheduled_job_info_to_dict(scheduled_job_info) -> Dict[str, Any]:
|
|
| 269 |
class HfJobsTool:
|
| 270 |
"""Tool for managing Hugging Face compute jobs using huggingface-hub library"""
|
| 271 |
|
| 272 |
-
def __init__(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
self.api = HfApi(token=hf_token)
|
| 274 |
self.namespace = namespace
|
|
|
|
| 275 |
|
| 276 |
async def execute(self, params: Dict[str, Any]) -> ToolResult:
|
| 277 |
"""Execute the specified operation"""
|
|
@@ -366,6 +373,8 @@ class HfJobsTool:
|
|
| 366 |
# Stream logs in real-time
|
| 367 |
for log_line in logs_gen:
|
| 368 |
print("\t" + log_line)
|
|
|
|
|
|
|
| 369 |
all_logs.append(log_line)
|
| 370 |
|
| 371 |
# If we get here, streaming completed normally
|
|
@@ -961,10 +970,22 @@ HF_JOBS_TOOL_SPEC = {
|
|
| 961 |
}
|
| 962 |
|
| 963 |
|
| 964 |
-
async def hf_jobs_handler(
|
|
|
|
|
|
|
| 965 |
"""Handler for agent tool router"""
|
| 966 |
try:
|
| 967 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 968 |
result = await tool.execute(arguments)
|
| 969 |
return result["formatted"], not result.get("isError", False)
|
| 970 |
except Exception as e:
|
|
|
|
| 9 |
import http.client
|
| 10 |
import os
|
| 11 |
import re
|
| 12 |
+
from typing import Any, Dict, Literal, Optional, Callable, Awaitable
|
| 13 |
|
| 14 |
import httpx
|
| 15 |
from huggingface_hub import HfApi
|
| 16 |
from huggingface_hub.utils import HfHubHTTPError
|
| 17 |
|
| 18 |
+
from agent.core.session import Event
|
| 19 |
from agent.tools.types import ToolResult
|
| 20 |
from agent.tools.utilities import (
|
| 21 |
format_job_details,
|
|
|
|
| 270 |
class HfJobsTool:
|
| 271 |
"""Tool for managing Hugging Face compute jobs using huggingface-hub library"""
|
| 272 |
|
| 273 |
+
def __init__(
|
| 274 |
+
self,
|
| 275 |
+
hf_token: Optional[str] = None,
|
| 276 |
+
namespace: Optional[str] = None,
|
| 277 |
+
log_callback: Optional[Callable[[str], Awaitable[None]]] = None,
|
| 278 |
+
):
|
| 279 |
self.api = HfApi(token=hf_token)
|
| 280 |
self.namespace = namespace
|
| 281 |
+
self.log_callback = log_callback
|
| 282 |
|
| 283 |
async def execute(self, params: Dict[str, Any]) -> ToolResult:
|
| 284 |
"""Execute the specified operation"""
|
|
|
|
| 373 |
# Stream logs in real-time
|
| 374 |
for log_line in logs_gen:
|
| 375 |
print("\t" + log_line)
|
| 376 |
+
if self.log_callback:
|
| 377 |
+
await self.log_callback(log_line)
|
| 378 |
all_logs.append(log_line)
|
| 379 |
|
| 380 |
# If we get here, streaming completed normally
|
|
|
|
| 970 |
}
|
| 971 |
|
| 972 |
|
| 973 |
+
async def hf_jobs_handler(
|
| 974 |
+
arguments: Dict[str, Any], session: Any = None
|
| 975 |
+
) -> tuple[str, bool]:
|
| 976 |
"""Handler for agent tool router"""
|
| 977 |
try:
|
| 978 |
+
|
| 979 |
+
async def log_callback(log: str):
|
| 980 |
+
if session:
|
| 981 |
+
await session.send_event(
|
| 982 |
+
Event(event_type="tool_log", data={"tool": "hf_jobs", "log": log})
|
| 983 |
+
)
|
| 984 |
+
|
| 985 |
+
tool = HfJobsTool(
|
| 986 |
+
namespace=os.environ.get("HF_NAMESPACE", ""),
|
| 987 |
+
log_callback=log_callback if session else None,
|
| 988 |
+
)
|
| 989 |
result = await tool.execute(arguments)
|
| 990 |
return result["formatted"], not result.get("isError", False)
|
| 991 |
except Exception as e:
|
frontend/src/hooks/useAgentWebSocket.ts
CHANGED
|
@@ -115,6 +115,39 @@ export function useAgentWebSocket({
|
|
| 115 |
break;
|
| 116 |
}
|
| 117 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
case 'approval_required': {
|
| 119 |
const tools = event.data?.tools as Array<{
|
| 120 |
tool: string;
|
|
|
|
| 115 |
break;
|
| 116 |
}
|
| 117 |
|
| 118 |
+
case 'tool_log': {
|
| 119 |
+
const toolName = (event.data?.tool as string) || 'unknown';
|
| 120 |
+
const log = (event.data?.log as string) || '';
|
| 121 |
+
|
| 122 |
+
if (toolName === 'hf_jobs') {
|
| 123 |
+
const currentPanel = useAgentStore.getState().panelContent;
|
| 124 |
+
|
| 125 |
+
// If we are already showing logs, append
|
| 126 |
+
// If we are showing "Compute Job Script", overwrite/switch to logs
|
| 127 |
+
// Otherwise, initialize
|
| 128 |
+
|
| 129 |
+
let newContent = log;
|
| 130 |
+
if (currentPanel?.title === 'Job Logs') {
|
| 131 |
+
newContent = currentPanel.content + '\n' + log;
|
| 132 |
+
} else if (currentPanel?.title === 'Compute Job Script') {
|
| 133 |
+
// We were showing the script, now logs start.
|
| 134 |
+
// Maybe we want to clear and start showing logs.
|
| 135 |
+
newContent = '--- Starting execution ---\n' + log;
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
setPanelContent({
|
| 139 |
+
title: 'Job Logs',
|
| 140 |
+
content: newContent,
|
| 141 |
+
language: 'text'
|
| 142 |
+
});
|
| 143 |
+
|
| 144 |
+
if (!useLayoutStore.getState().isRightPanelOpen) {
|
| 145 |
+
setRightPanelOpen(true);
|
| 146 |
+
}
|
| 147 |
+
}
|
| 148 |
+
break;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
case 'approval_required': {
|
| 152 |
const tools = event.data?.tools as Array<{
|
| 153 |
tool: string;
|
frontend/src/types/events.ts
CHANGED
|
@@ -8,6 +8,7 @@ export type EventType =
|
|
| 8 |
| 'assistant_message'
|
| 9 |
| 'tool_call'
|
| 10 |
| 'tool_output'
|
|
|
|
| 11 |
| 'approval_required'
|
| 12 |
| 'turn_complete'
|
| 13 |
| 'compacted'
|
|
@@ -44,6 +45,11 @@ export interface ToolOutputEventData {
|
|
| 44 |
success: boolean;
|
| 45 |
}
|
| 46 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
export interface ApprovalRequiredEventData {
|
| 48 |
tools: ApprovalToolItem[];
|
| 49 |
count: number;
|
|
|
|
| 8 |
| 'assistant_message'
|
| 9 |
| 'tool_call'
|
| 10 |
| 'tool_output'
|
| 11 |
+
| 'tool_log'
|
| 12 |
| 'approval_required'
|
| 13 |
| 'turn_complete'
|
| 14 |
| 'compacted'
|
|
|
|
| 45 |
success: boolean;
|
| 46 |
}
|
| 47 |
|
| 48 |
+
export interface ToolLogEventData {
|
| 49 |
+
tool: string;
|
| 50 |
+
log: string;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
export interface ApprovalRequiredEventData {
|
| 54 |
tools: ApprovalToolItem[];
|
| 55 |
count: number;
|