Henri Bonamy commited on
Commit
226dea1
·
1 Parent(s): b1d24de

log streaming

Browse files
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(tool_name, tool_args)
 
 
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__(self, hf_token: Optional[str] = None, namespace: Optional[str] = None):
 
 
 
 
 
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(arguments: Dict[str, Any]) -> tuple[str, bool]:
 
 
965
  """Handler for agent tool router"""
966
  try:
967
- tool = HfJobsTool(namespace=os.environ.get("HF_NAMESPACE", ""))
 
 
 
 
 
 
 
 
 
 
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;