Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions holmes/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,10 @@ def get_runbook_catalog() -> Optional[RunbookCatalog]:
return runbook_catalog

def create_console_tool_executor(
self, dal: Optional["SupabaseDal"], refresh_status: bool = False
self,
dal: Optional["SupabaseDal"],
refresh_status: bool = False,
quiet: bool = False,
) -> ToolExecutor:
"""
Creates a ToolExecutor instance configured for CLI usage. This executor manages the available tools
Expand All @@ -274,7 +277,7 @@ def create_console_tool_executor(
3. Custom toolsets from config files which can not override built-in toolsets
"""
cli_toolsets = self.toolset_manager.list_console_toolsets(
dal=dal, refresh_status=refresh_status
dal=dal, refresh_status=refresh_status, quiet=quiet
)
return ToolExecutor(cli_toolsets)

Expand All @@ -301,8 +304,9 @@ def create_console_toolcalling_llm(
dal: Optional["SupabaseDal"] = None,
refresh_toolsets: bool = False,
tracer=None,
quiet: bool = False,
) -> "ToolCallingLLM":
tool_executor = self.create_console_tool_executor(dal, refresh_toolsets)
tool_executor = self.create_console_tool_executor(dal, refresh_toolsets, quiet)
from holmes.core.tool_calling_llm import ToolCallingLLM

return ToolCallingLLM(
Expand Down
25 changes: 19 additions & 6 deletions holmes/core/tool_calling_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,9 +241,14 @@ def messages_call(
post_process_prompt: Optional[str] = None,
response_format: Optional[Union[dict, Type[BaseModel]]] = None,
trace_span=DummySpan(),
quiet: bool = False,
) -> LLMResult:
return self.call(
messages, post_process_prompt, response_format, trace_span=trace_span
messages,
post_process_prompt,
response_format,
trace_span=trace_span,
quiet=quiet,
)

@sentry_sdk.trace
Expand All @@ -256,6 +261,7 @@ def call( # type: ignore
sections: Optional[InputSectionsDataType] = None,
trace_span=DummySpan(),
tool_number_offset: int = 0,
quiet: bool = False,
) -> LLMResult:
perf_timing = PerformanceTiming("tool_calling_llm.call")
tool_calls = [] # type: ignore
Expand Down Expand Up @@ -377,9 +383,10 @@ def call( # type: ignore

if text_response and text_response.strip():
logging.info(f"[bold {AI_COLOR}]AI:[/bold {AI_COLOR}] {text_response}")
logging.info(
f"The AI requested [bold]{len(tools_to_call) if tools_to_call else 0}[/bold] tool call(s)."
)
if not quiet:
logging.info(
f"The AI requested [bold]{len(tools_to_call) if tools_to_call else 0}[/bold] tool call(s)."
)
perf_timing.measure("pre-tool-calls")
with concurrent.futures.ThreadPoolExecutor(max_workers=16) as executor:
futures = []
Expand All @@ -392,6 +399,7 @@ def call( # type: ignore
previous_tool_calls=tool_calls,
trace_span=trace_span,
tool_number=tool_number_offset + tool_index,
quiet=quiet,
)
)

Expand All @@ -404,7 +412,7 @@ def call( # type: ignore
perf_timing.measure(f"tool completed {tool_call_result.tool_name}")

# Add a blank line after all tools in this batch complete
if tools_to_call:
if tools_to_call and not quiet:
logging.info("")

raise Exception(f"Too many LLM calls - exceeded max_steps: {i}/{max_steps}")
Expand All @@ -415,6 +423,7 @@ def _invoke_tool(
previous_tool_calls: list[dict],
trace_span=DummySpan(),
tool_number=None,
quiet: bool = False,
) -> ToolCallResult:
# Handle the union type - ChatCompletionMessageToolCall can be either
# ChatCompletionMessageFunctionToolCall (with 'function' field and type='function')
Expand Down Expand Up @@ -475,7 +484,9 @@ def _invoke_tool(
tool_calls=previous_tool_calls,
)
if not tool_response:
tool_response = tool.invoke(tool_params, tool_number=tool_number)
tool_response = tool.invoke(
tool_params, tool_number=tool_number, quiet=quiet
)

if not isinstance(tool_response, StructuredToolResult):
# Should never be needed but ensure Holmes does not crash if one of the tools does not return the right type
Expand Down Expand Up @@ -582,6 +593,7 @@ def call_stream(
response_format: Optional[Union[dict, Type[BaseModel]]] = None,
sections: Optional[InputSectionsDataType] = None,
msgs: Optional[list[dict]] = None,
quiet: bool = False,
):
"""
This function DOES NOT call llm.completion(stream=true).
Expand Down Expand Up @@ -688,6 +700,7 @@ def call_stream(
previous_tool_calls=tool_calls,
trace_span=DummySpan(), # Streaming mode doesn't support tracing yet
tool_number=tool_index,
quiet=quiet,
)
)
yield StreamMessage(
Expand Down
24 changes: 14 additions & 10 deletions holmes/core/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,13 @@ def get_openai_format(self, target_model: str):
)

def invoke(
self, params: Dict, tool_number: Optional[int] = None
self, params: Dict, tool_number: Optional[int] = None, quiet: bool = False
) -> StructuredToolResult:
tool_number_str = f"#{tool_number} " if tool_number else ""
logging.info(
f"Running tool {tool_number_str}[bold]{self.name}[/bold]: {self.get_parameterized_one_liner(params)}"
)
if not quiet:
logging.info(
f"Running tool {tool_number_str}[bold]{self.name}[/bold]: {self.get_parameterized_one_liner(params)}"
)
start_time = time.time()
result = self._invoke(params)
result.icon_url = self.icon_url
Expand All @@ -165,9 +166,10 @@ def invoke(
)
show_hint = f"/show {tool_number}" if tool_number else "/show"
line_count = output_str.count("\n") + 1 if output_str else 0
logging.info(
f" [dim]Finished {tool_number_str}in {elapsed:.2f}s, output length: {len(output_str):,} characters ({line_count:,} lines) - {show_hint} to view contents[/dim]"
)
if not quiet:
logging.info(
f" [dim]Finished {tool_number_str}in {elapsed:.2f}s, output length: {len(output_str):,} characters ({line_count:,} lines) - {show_hint} to view contents[/dim]"
)
return result

@abstractmethod
Expand Down Expand Up @@ -415,7 +417,7 @@ def interpolate_command(self, command: str) -> str:

return interpolated_command

def check_prerequisites(self):
def check_prerequisites(self, quiet: bool = False):
self.status = ToolsetStatusEnum.ENABLED

for prereq in self.prerequisites:
Expand Down Expand Up @@ -466,11 +468,13 @@ def check_prerequisites(self):
self.status == ToolsetStatusEnum.DISABLED
or self.status == ToolsetStatusEnum.FAILED
):
logging.info(f"❌ Toolset {self.name}: {self.error}")
if not quiet:
logging.info(f"❌ Toolset {self.name}: {self.error}")
# no point checking further prerequisites if one failed
return

logging.info(f"βœ… Toolset {self.name}")
if not quiet:
logging.info(f"βœ… Toolset {self.name}")

@abstractmethod
def get_example_config(self) -> Dict[str, Any]:
Expand Down
37 changes: 26 additions & 11 deletions holmes/core/toolset_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def _list_all_toolsets(
check_prerequisites=True,
enable_all_toolsets=False,
toolset_tags: Optional[List[ToolsetTag]] = None,
quiet: bool = False,
) -> List[Toolset]:
"""
List all built-in and custom toolsets.
Expand Down Expand Up @@ -128,16 +129,16 @@ def _list_all_toolsets(
enabled_toolsets.append(toolset)
else:
toolset.status = ToolsetStatusEnum.DISABLED
self.check_toolset_prerequisites(enabled_toolsets)
self.check_toolset_prerequisites(enabled_toolsets, quiet=quiet)

return list(toolsets_by_name.values())

@classmethod
def check_toolset_prerequisites(cls, toolsets: list[Toolset]):
def check_toolset_prerequisites(cls, toolsets: list[Toolset], quiet: bool = False):
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
futures = []
for toolset in toolsets:
futures.append(executor.submit(toolset.check_prerequisites))
futures.append(executor.submit(toolset.check_prerequisites, quiet))

for _ in concurrent.futures.as_completed(futures):
pass
Expand Down Expand Up @@ -184,6 +185,7 @@ def refresh_toolset_status(
dal: Optional[SupabaseDal] = None,
enable_all_toolsets=False,
toolset_tags: Optional[List[ToolsetTag]] = None,
quiet: bool = False,
):
"""
Refresh the status of all toolsets and cache the status to a file.
Expand All @@ -199,6 +201,7 @@ def refresh_toolset_status(
check_prerequisites=True,
enable_all_toolsets=enable_all_toolsets,
toolset_tags=toolset_tags,
quiet=quiet,
)

if self.toolset_status_location and not os.path.exists(
Expand All @@ -215,14 +218,18 @@ def refresh_toolset_status(
for toolset in all_toolsets
]
json.dump(toolset_status, f, indent=2)
logging.info(f"Toolset statuses are cached to {self.toolset_status_location}")
if not quiet:
logging.info(
f"Toolset statuses are cached to {self.toolset_status_location}"
)

def load_toolset_with_status(
self,
dal: Optional[SupabaseDal] = None,
refresh_status: bool = False,
enable_all_toolsets=False,
toolset_tags: Optional[List[ToolsetTag]] = None,
quiet: bool = False,
) -> List[Toolset]:
"""
Load the toolset with status from the cache file.
Expand All @@ -232,9 +239,13 @@ def load_toolset_with_status(
"""

if not os.path.exists(self.toolset_status_location) or refresh_status:
logging.info("Refreshing available datasources (toolsets)")
if not quiet:
logging.info("Refreshing available datasources (toolsets)")
self.refresh_toolset_status(
dal, enable_all_toolsets=enable_all_toolsets, toolset_tags=toolset_tags
dal,
enable_all_toolsets=enable_all_toolsets,
toolset_tags=toolset_tags,
quiet=quiet,
)
using_cached = False
else:
Expand All @@ -249,7 +260,7 @@ def load_toolset_with_status(
cached_toolset["name"]: cached_toolset for cached_toolset in cached_toolsets
}
all_toolsets_with_status = self._list_all_toolsets(
dal=dal, check_prerequisites=False, toolset_tags=toolset_tags
dal=dal, check_prerequisites=False, toolset_tags=toolset_tags, quiet=quiet
)

enabled_toolsets_from_cache: List[Toolset] = []
Expand All @@ -272,7 +283,7 @@ def load_toolset_with_status(
and using_cached
):
enabled_toolsets_from_cache.append(toolset)
self.check_toolset_prerequisites(enabled_toolsets_from_cache)
self.check_toolset_prerequisites(enabled_toolsets_from_cache, quiet=quiet)

# CLI custom toolsets status are not cached, and their prerequisites are always checked whenever the CLI runs.
custom_toolsets_from_cli = self._load_toolsets_from_paths(
Expand All @@ -289,10 +300,10 @@ def load_toolset_with_status(
)
enabled_toolsets_from_cli.append(custom_toolset_from_cli)
# status of custom toolsets from cli is not cached, and we need to check prerequisites every time the cli runs.
self.check_toolset_prerequisites(enabled_toolsets_from_cli)
self.check_toolset_prerequisites(enabled_toolsets_from_cli, quiet=quiet)

all_toolsets_with_status.extend(custom_toolsets_from_cli)
if using_cached:
if using_cached and not quiet:
num_available_toolsets = len(
[toolset for toolset in all_toolsets_with_status if toolset.enabled]
)
Expand All @@ -302,7 +313,10 @@ def load_toolset_with_status(
return all_toolsets_with_status

def list_console_toolsets(
self, dal: Optional[SupabaseDal] = None, refresh_status=False
self,
dal: Optional[SupabaseDal] = None,
refresh_status=False,
quiet: bool = False,
) -> List[Toolset]:
"""
List all enabled toolsets that cli tools can use.
Expand All @@ -315,6 +329,7 @@ def list_console_toolsets(
refresh_status=refresh_status,
enable_all_toolsets=True,
toolset_tags=self.cli_tool_tags,
quiet=quiet,
)
return toolsets_with_status

Expand Down
13 changes: 8 additions & 5 deletions holmes/interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,7 @@ def run_interactive_loop(
tracer=None,
runbooks=None,
system_prompt_additions: Optional[str] = None,
quiet: bool = False,
check_version: bool = True,
) -> None:
# Initialize tracer - use DummyTracer if no tracer provided
Expand Down Expand Up @@ -901,11 +902,12 @@ def get_bottom_toolbar():

input_prompt = [("class:prompt", "User: ")]

console.print(WELCOME_BANNER)
if initial_user_input:
console.print(
f"[bold {USER_COLOR}]User:[/bold {USER_COLOR}] {initial_user_input}"
)
if not quiet:
console.print(WELCOME_BANNER)
if initial_user_input:
console.print(
f"[bold {USER_COLOR}]User:[/bold {USER_COLOR}] {initial_user_input}"
)
messages = None
last_response = None
all_tool_calls_history: List[
Expand Down Expand Up @@ -1022,6 +1024,7 @@ def get_bottom_toolbar():
post_processing_prompt,
trace_span=trace_span,
tool_number_offset=len(all_tool_calls_history),
quiet=quiet,
)
trace_span.log(
output=response.result,
Expand Down
Loading
Loading