holmesgpt 0.13.2__py3-none-any.whl → 0.18.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- holmes/__init__.py +3 -5
- holmes/clients/robusta_client.py +20 -6
- holmes/common/env_vars.py +58 -3
- holmes/common/openshift.py +1 -1
- holmes/config.py +123 -148
- holmes/core/conversations.py +71 -15
- holmes/core/feedback.py +191 -0
- holmes/core/investigation.py +31 -39
- holmes/core/investigation_structured_output.py +3 -3
- holmes/core/issue.py +1 -1
- holmes/core/llm.py +508 -88
- holmes/core/models.py +108 -4
- holmes/core/openai_formatting.py +14 -1
- holmes/core/prompt.py +48 -3
- holmes/core/runbooks.py +1 -0
- holmes/core/safeguards.py +8 -6
- holmes/core/supabase_dal.py +295 -100
- holmes/core/tool_calling_llm.py +489 -428
- holmes/core/tools.py +325 -56
- holmes/core/tools_utils/token_counting.py +21 -0
- holmes/core/tools_utils/tool_context_window_limiter.py +40 -0
- holmes/core/tools_utils/tool_executor.py +0 -13
- holmes/core/tools_utils/toolset_utils.py +1 -0
- holmes/core/toolset_manager.py +191 -5
- holmes/core/tracing.py +19 -3
- holmes/core/transformers/__init__.py +23 -0
- holmes/core/transformers/base.py +63 -0
- holmes/core/transformers/llm_summarize.py +175 -0
- holmes/core/transformers/registry.py +123 -0
- holmes/core/transformers/transformer.py +32 -0
- holmes/core/truncation/compaction.py +94 -0
- holmes/core/truncation/dal_truncation_utils.py +23 -0
- holmes/core/truncation/input_context_window_limiter.py +219 -0
- holmes/interactive.py +228 -31
- holmes/main.py +23 -40
- holmes/plugins/interfaces.py +2 -1
- holmes/plugins/prompts/__init__.py +2 -1
- holmes/plugins/prompts/_fetch_logs.jinja2 +31 -6
- holmes/plugins/prompts/_general_instructions.jinja2 +1 -2
- holmes/plugins/prompts/_runbook_instructions.jinja2 +24 -12
- holmes/plugins/prompts/base_user_prompt.jinja2 +7 -0
- holmes/plugins/prompts/conversation_history_compaction.jinja2 +89 -0
- holmes/plugins/prompts/generic_ask.jinja2 +0 -4
- holmes/plugins/prompts/generic_ask_conversation.jinja2 +0 -1
- holmes/plugins/prompts/generic_ask_for_issue_conversation.jinja2 +0 -1
- holmes/plugins/prompts/generic_investigation.jinja2 +0 -1
- holmes/plugins/prompts/investigation_procedure.jinja2 +50 -1
- holmes/plugins/prompts/kubernetes_workload_ask.jinja2 +0 -1
- holmes/plugins/prompts/kubernetes_workload_chat.jinja2 +0 -1
- holmes/plugins/runbooks/__init__.py +145 -17
- holmes/plugins/runbooks/catalog.json +2 -0
- holmes/plugins/sources/github/__init__.py +4 -2
- holmes/plugins/sources/prometheus/models.py +1 -0
- holmes/plugins/toolsets/__init__.py +44 -27
- holmes/plugins/toolsets/aks-node-health.yaml +46 -0
- holmes/plugins/toolsets/aks.yaml +64 -0
- holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py +38 -47
- holmes/plugins/toolsets/azure_sql/apis/alert_monitoring_api.py +3 -2
- holmes/plugins/toolsets/azure_sql/apis/azure_sql_api.py +2 -1
- holmes/plugins/toolsets/azure_sql/apis/connection_failure_api.py +3 -2
- holmes/plugins/toolsets/azure_sql/apis/connection_monitoring_api.py +3 -1
- holmes/plugins/toolsets/azure_sql/apis/storage_analysis_api.py +3 -1
- holmes/plugins/toolsets/azure_sql/azure_sql_toolset.py +12 -13
- holmes/plugins/toolsets/azure_sql/tools/analyze_connection_failures.py +15 -12
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_connections.py +15 -12
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_health_status.py +11 -11
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_performance.py +11 -9
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_storage.py +15 -12
- holmes/plugins/toolsets/azure_sql/tools/get_active_alerts.py +15 -15
- holmes/plugins/toolsets/azure_sql/tools/get_slow_queries.py +11 -8
- holmes/plugins/toolsets/azure_sql/tools/get_top_cpu_queries.py +11 -8
- holmes/plugins/toolsets/azure_sql/tools/get_top_data_io_queries.py +11 -8
- holmes/plugins/toolsets/azure_sql/tools/get_top_log_io_queries.py +11 -8
- holmes/plugins/toolsets/azure_sql/utils.py +0 -32
- holmes/plugins/toolsets/bash/argocd/__init__.py +3 -3
- holmes/plugins/toolsets/bash/aws/__init__.py +4 -4
- holmes/plugins/toolsets/bash/azure/__init__.py +4 -4
- holmes/plugins/toolsets/bash/bash_toolset.py +11 -15
- holmes/plugins/toolsets/bash/common/bash.py +23 -13
- holmes/plugins/toolsets/bash/common/bash_command.py +1 -1
- holmes/plugins/toolsets/bash/common/stringify.py +1 -1
- holmes/plugins/toolsets/bash/kubectl/__init__.py +2 -1
- holmes/plugins/toolsets/bash/kubectl/constants.py +0 -1
- holmes/plugins/toolsets/bash/kubectl/kubectl_get.py +3 -4
- holmes/plugins/toolsets/bash/parse_command.py +12 -13
- holmes/plugins/toolsets/cilium.yaml +284 -0
- holmes/plugins/toolsets/connectivity_check.py +124 -0
- holmes/plugins/toolsets/coralogix/api.py +132 -119
- holmes/plugins/toolsets/coralogix/coralogix.jinja2 +14 -0
- holmes/plugins/toolsets/coralogix/toolset_coralogix.py +219 -0
- holmes/plugins/toolsets/coralogix/utils.py +15 -79
- holmes/plugins/toolsets/datadog/datadog_api.py +525 -26
- holmes/plugins/toolsets/datadog/datadog_logs_instructions.jinja2 +55 -11
- holmes/plugins/toolsets/datadog/datadog_metrics_instructions.jinja2 +3 -3
- holmes/plugins/toolsets/datadog/datadog_models.py +59 -0
- holmes/plugins/toolsets/datadog/datadog_url_utils.py +213 -0
- holmes/plugins/toolsets/datadog/instructions_datadog_traces.jinja2 +165 -28
- holmes/plugins/toolsets/datadog/toolset_datadog_general.py +417 -241
- holmes/plugins/toolsets/datadog/toolset_datadog_logs.py +234 -214
- holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py +167 -79
- holmes/plugins/toolsets/datadog/toolset_datadog_traces.py +374 -363
- holmes/plugins/toolsets/elasticsearch/__init__.py +6 -0
- holmes/plugins/toolsets/elasticsearch/elasticsearch.py +834 -0
- holmes/plugins/toolsets/elasticsearch/opensearch_ppl_query_docs.jinja2 +1616 -0
- holmes/plugins/toolsets/elasticsearch/opensearch_query_assist.py +78 -0
- holmes/plugins/toolsets/elasticsearch/opensearch_query_assist_instructions.jinja2 +223 -0
- holmes/plugins/toolsets/git.py +54 -50
- holmes/plugins/toolsets/grafana/base_grafana_toolset.py +16 -4
- holmes/plugins/toolsets/grafana/common.py +13 -29
- holmes/plugins/toolsets/grafana/grafana_tempo_api.py +455 -0
- holmes/plugins/toolsets/grafana/loki/instructions.jinja2 +25 -0
- holmes/plugins/toolsets/grafana/loki/toolset_grafana_loki.py +191 -0
- holmes/plugins/toolsets/grafana/loki_api.py +4 -0
- holmes/plugins/toolsets/grafana/toolset_grafana.py +293 -89
- holmes/plugins/toolsets/grafana/toolset_grafana_dashboard.jinja2 +49 -0
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.jinja2 +246 -11
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +820 -292
- holmes/plugins/toolsets/grafana/trace_parser.py +4 -3
- holmes/plugins/toolsets/internet/internet.py +15 -16
- holmes/plugins/toolsets/internet/notion.py +9 -11
- holmes/plugins/toolsets/investigator/core_investigation.py +44 -36
- holmes/plugins/toolsets/investigator/model.py +3 -1
- holmes/plugins/toolsets/json_filter_mixin.py +134 -0
- holmes/plugins/toolsets/kafka.py +36 -42
- holmes/plugins/toolsets/kubernetes.yaml +317 -113
- holmes/plugins/toolsets/kubernetes_logs.py +9 -9
- holmes/plugins/toolsets/kubernetes_logs.yaml +32 -0
- holmes/plugins/toolsets/logging_utils/logging_api.py +94 -8
- holmes/plugins/toolsets/mcp/toolset_mcp.py +218 -64
- holmes/plugins/toolsets/newrelic/new_relic_api.py +165 -0
- holmes/plugins/toolsets/newrelic/newrelic.jinja2 +65 -0
- holmes/plugins/toolsets/newrelic/newrelic.py +320 -0
- holmes/plugins/toolsets/openshift.yaml +283 -0
- holmes/plugins/toolsets/prometheus/prometheus.py +1202 -421
- holmes/plugins/toolsets/prometheus/prometheus_instructions.jinja2 +54 -5
- holmes/plugins/toolsets/prometheus/utils.py +28 -0
- holmes/plugins/toolsets/rabbitmq/api.py +23 -4
- holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py +13 -14
- holmes/plugins/toolsets/robusta/robusta.py +239 -68
- holmes/plugins/toolsets/robusta/robusta_instructions.jinja2 +26 -9
- holmes/plugins/toolsets/runbook/runbook_fetcher.py +157 -27
- holmes/plugins/toolsets/service_discovery.py +1 -1
- holmes/plugins/toolsets/servicenow_tables/instructions.jinja2 +83 -0
- holmes/plugins/toolsets/servicenow_tables/servicenow_tables.py +426 -0
- holmes/plugins/toolsets/utils.py +88 -0
- holmes/utils/config_utils.py +91 -0
- holmes/utils/connection_utils.py +31 -0
- holmes/utils/console/result.py +10 -0
- holmes/utils/default_toolset_installation_guide.jinja2 +1 -22
- holmes/utils/env.py +7 -0
- holmes/utils/file_utils.py +2 -1
- holmes/utils/global_instructions.py +60 -11
- holmes/utils/holmes_status.py +6 -4
- holmes/utils/holmes_sync_toolsets.py +0 -2
- holmes/utils/krr_utils.py +188 -0
- holmes/utils/log.py +15 -0
- holmes/utils/markdown_utils.py +2 -3
- holmes/utils/memory_limit.py +58 -0
- holmes/utils/sentry_helper.py +64 -0
- holmes/utils/stream.py +69 -8
- holmes/utils/tags.py +4 -3
- holmes/version.py +37 -15
- holmesgpt-0.18.4.dist-info/LICENSE +178 -0
- {holmesgpt-0.13.2.dist-info → holmesgpt-0.18.4.dist-info}/METADATA +35 -31
- holmesgpt-0.18.4.dist-info/RECORD +258 -0
- holmes/core/performance_timing.py +0 -72
- holmes/plugins/toolsets/aws.yaml +0 -80
- holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py +0 -112
- holmes/plugins/toolsets/datadog/datadog_traces_formatter.py +0 -310
- holmes/plugins/toolsets/datadog/toolset_datadog_rds.py +0 -739
- holmes/plugins/toolsets/grafana/grafana_api.py +0 -42
- holmes/plugins/toolsets/grafana/tempo_api.py +0 -124
- holmes/plugins/toolsets/grafana/toolset_grafana_loki.py +0 -110
- holmes/plugins/toolsets/newrelic.py +0 -231
- holmes/plugins/toolsets/opensearch/opensearch.py +0 -257
- holmes/plugins/toolsets/opensearch/opensearch_logs.py +0 -161
- holmes/plugins/toolsets/opensearch/opensearch_traces.py +0 -218
- holmes/plugins/toolsets/opensearch/opensearch_traces_instructions.jinja2 +0 -12
- holmes/plugins/toolsets/opensearch/opensearch_utils.py +0 -166
- holmes/plugins/toolsets/servicenow/install.md +0 -37
- holmes/plugins/toolsets/servicenow/instructions.jinja2 +0 -3
- holmes/plugins/toolsets/servicenow/servicenow.py +0 -219
- holmes/utils/keygen_utils.py +0 -6
- holmesgpt-0.13.2.dist-info/LICENSE.txt +0 -21
- holmesgpt-0.13.2.dist-info/RECORD +0 -234
- /holmes/plugins/toolsets/{opensearch → newrelic}/__init__.py +0 -0
- {holmesgpt-0.13.2.dist-info → holmesgpt-0.18.4.dist-info}/WHEEL +0 -0
- {holmesgpt-0.13.2.dist-info → holmesgpt-0.18.4.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from typing import Any, Dict
|
|
4
|
+
|
|
5
|
+
from holmes.core.tools import (
|
|
6
|
+
StructuredToolResult,
|
|
7
|
+
StructuredToolResultStatus,
|
|
8
|
+
Tool,
|
|
9
|
+
ToolInvokeContext,
|
|
10
|
+
ToolParameter,
|
|
11
|
+
Toolset,
|
|
12
|
+
ToolsetEnvironmentPrerequisite,
|
|
13
|
+
ToolsetTag,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PplQueryAssistTool(Tool):
|
|
18
|
+
def __init__(self, toolset: "OpenSearchQueryAssistToolset"):
|
|
19
|
+
super().__init__(
|
|
20
|
+
name="opensearch_ppl_query_assist",
|
|
21
|
+
description="Generate valid OpenSearch Piped Processing Language (PPL) queries to suggest to users for execution",
|
|
22
|
+
parameters={
|
|
23
|
+
"query": ToolParameter(
|
|
24
|
+
description="Valid OpenSearch Piped Processing Language (PPL) query to suggest to users for execution",
|
|
25
|
+
type="string",
|
|
26
|
+
required=True,
|
|
27
|
+
),
|
|
28
|
+
},
|
|
29
|
+
)
|
|
30
|
+
self._toolset = toolset
|
|
31
|
+
|
|
32
|
+
def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
|
|
33
|
+
try:
|
|
34
|
+
query = params.get("query", "")
|
|
35
|
+
response_data = {"query": query}
|
|
36
|
+
return StructuredToolResult(
|
|
37
|
+
status=StructuredToolResultStatus.SUCCESS,
|
|
38
|
+
data=response_data,
|
|
39
|
+
params=params,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
except Exception as e:
|
|
43
|
+
logging.exception(f"error using {self.name} tool")
|
|
44
|
+
return StructuredToolResult(
|
|
45
|
+
status=StructuredToolResultStatus.ERROR,
|
|
46
|
+
error=f"Failed to generate PPL query: {str(e)}",
|
|
47
|
+
params=params,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def get_parameterized_one_liner(self, params: Dict) -> str:
|
|
51
|
+
query = params.get("query", "")
|
|
52
|
+
return f"OpenSearchQueryToolset: Query ({query})"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class OpenSearchQueryAssistToolset(Toolset):
|
|
56
|
+
"""OpenSearch query assist with PPL queries"""
|
|
57
|
+
|
|
58
|
+
def __init__(self):
|
|
59
|
+
super().__init__(
|
|
60
|
+
name="opensearch/query_assist",
|
|
61
|
+
description="OpenSearch query assist with PPL queries.",
|
|
62
|
+
experimental=True,
|
|
63
|
+
icon_url="https://opensearch.org/assets/brand/PNG/Mark/opensearch_mark_default.png",
|
|
64
|
+
tools=[PplQueryAssistTool(self)],
|
|
65
|
+
tags=[ToolsetTag.CORE],
|
|
66
|
+
prerequisites=[ToolsetEnvironmentPrerequisite(env=["OPENSEARCH_URL"])],
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def get_example_config(self) -> Dict[str, Any]:
|
|
70
|
+
return {"opensearch_url": "http://localhost:9200"}
|
|
71
|
+
|
|
72
|
+
def _reload_instructions(self):
|
|
73
|
+
template_file_path = os.path.abspath(
|
|
74
|
+
os.path.join(
|
|
75
|
+
os.path.dirname(__file__), "opensearch_query_assist_instructions.jinja2"
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
self._load_llm_instructions(jinja_template=f"file://{template_file_path}")
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# Query Generation
|
|
2
|
+
You have access to the opensearch_ppl_query_assist tool to help you generate your valid, accurate OpenSearch Piped Processing Language (PPL) queries.
|
|
3
|
+
DO NOT PROVIDE INVALID QUERIES. ALWAYS CHECK YOUR QUERY WITH VALID QUERIES FIRST.
|
|
4
|
+
|
|
5
|
+
Once a valid query is generated, you MUST provide a concise, but informative breakdown of each part of the query structure
|
|
6
|
+
|
|
7
|
+
## CRITICAL: Query Intent Detection
|
|
8
|
+
|
|
9
|
+
ALWAYS check if the user's question is about:
|
|
10
|
+
|
|
11
|
+
* Log Analysis: Errors, warnings, messages, patterns, tool usage
|
|
12
|
+
* Metrics Analysis: Performance, latency, throughput, resource usage
|
|
13
|
+
* Time-based Analysis: "Last X hours/days", "recent", "today", "since"
|
|
14
|
+
* Aggregation Requests: Count, sum, average, top, frequency
|
|
15
|
+
* Troubleshooting: Issues, problems, failures, debugging
|
|
16
|
+
|
|
17
|
+
If ANY of the above apply → Generate PPL query IMMEDIATELY and use the OpenSearch Dashboards Page State
|
|
18
|
+
|
|
19
|
+
### Example GOOD response:
|
|
20
|
+
I've retrieved your current query from the query bar `source=logs-otel-v1* | STAT count() BY severityText` and it
|
|
21
|
+
appears there is a typo in "STAT", it should be "STATS". Below is the fixed query:
|
|
22
|
+
```
|
|
23
|
+
source=logs-otel-v1* | STATS count() BY severityText
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## CRITICAL: OpenSearch Dashboards Page State
|
|
28
|
+
User may be using this agent from OpenSearch Dashboards (OSD) for which provides the current page state.
|
|
29
|
+
It may be included in the conversation history as a system message.
|
|
30
|
+
|
|
31
|
+
IMPORTANT: YOU CAN USE THE CURRENT USE QUERY TO HELP ENHANCE/MODIFY/FIX/SUGGEST VALID QUERY USING THE SAME INDEX PATTERN
|
|
32
|
+
REFER TO "Core PPL Commands" FOR SYNTAX
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
## OpenSearch PPL Query Language
|
|
36
|
+
|
|
37
|
+
### PPL (Piped Processing Language) Overview
|
|
38
|
+
PPL is OpenSearch's query language for analyzing logs, metrics, and traces. It uses a pipe-based syntax similar to Unix commands, processing data through sequential transformations.
|
|
39
|
+
|
|
40
|
+
### Core PPL Commands
|
|
41
|
+
|
|
42
|
+
**Data Source & Search:**
|
|
43
|
+
- `source=<index>` or `search source=<index>` - Specify data source
|
|
44
|
+
- `source=<cluster>:<index>` - Cross-cluster search
|
|
45
|
+
- `| where <condition>` - Filter results
|
|
46
|
+
- `| fields <field-list>` - Project specific fields
|
|
47
|
+
- `| fields - <field-list>` - Exclude specific fields
|
|
48
|
+
|
|
49
|
+
**Data Transformation:**
|
|
50
|
+
- `| stats <aggregation> by <field>` - Aggregate data (count(), sum(), avg(), min(), max())
|
|
51
|
+
- `| eval <field>=<expression>` - Create calculated fields
|
|
52
|
+
- `| sort [+|-] <field>` - Sort results (+ ascending, - descending)
|
|
53
|
+
- `| head <n>` - Return first n results
|
|
54
|
+
- `| tail <n>` - Return last n results
|
|
55
|
+
- `| dedup <field-list>` - Remove duplicates
|
|
56
|
+
|
|
57
|
+
**Advanced Analysis:**
|
|
58
|
+
- `| top [N] <field>` - Find most common values
|
|
59
|
+
- `| rare [N] <field>` - Find least common values
|
|
60
|
+
- `| parse <field> <regex>` - Extract fields using regex patterns
|
|
61
|
+
- `| grok <field> <pattern>` - Parse using grok patterns
|
|
62
|
+
- `| patterns <field> [SIMPLE_PATTERN|BRAIN]` - Extract log patterns
|
|
63
|
+
|
|
64
|
+
**Time Series:**
|
|
65
|
+
- `| trendline SMA(<period>, <field>)` - Calculate moving averages
|
|
66
|
+
- `| fillnull with <value> in <fields>` - Replace null values
|
|
67
|
+
|
|
68
|
+
**Joins & Lookups:**
|
|
69
|
+
- `| join <table>` - Join with another dataset
|
|
70
|
+
- `| lookup <table> <field>` - Enrich with lookup data (requires Calcite)
|
|
71
|
+
|
|
72
|
+
**Pattern Extraction:**
|
|
73
|
+
- `| patterns message BRAIN` - Semantic log pattern extraction
|
|
74
|
+
- `| patterns new_field='extracted' pattern='[0-9]' message` - Custom regex patterns
|
|
75
|
+
|
|
76
|
+
### PPL Query Examples for Observability
|
|
77
|
+
|
|
78
|
+
**Error Analysis:**
|
|
79
|
+
```ppl
|
|
80
|
+
source=ai-agent-logs-*
|
|
81
|
+
| where level="ERROR"
|
|
82
|
+
| stats count() by message
|
|
83
|
+
| sort - count
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Service Latency Analysis:**
|
|
87
|
+
```ppl
|
|
88
|
+
source=traces
|
|
89
|
+
| where service="checkout"
|
|
90
|
+
| stats avg(duration) as avg_latency, max(duration) as max_latency by endpoint
|
|
91
|
+
| where avg_latency > 100
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Log Pattern Detection:**
|
|
95
|
+
```ppl
|
|
96
|
+
source=ai-agent-audit-logs-*
|
|
97
|
+
| patterns message BRAIN
|
|
98
|
+
| stats count() by patterns_field
|
|
99
|
+
| top 10 patterns_field
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Time-based Aggregation:**
|
|
103
|
+
```ppl
|
|
104
|
+
source=metrics
|
|
105
|
+
| eval hour=date_format(timestamp, 'HH')
|
|
106
|
+
| stats avg(cpu_usage) by hour, host
|
|
107
|
+
| sort hour
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Multi-field Correlation:**
|
|
111
|
+
```ppl
|
|
112
|
+
source=ai-agent-logs-*
|
|
113
|
+
| parse message '.*thread_id=(?<tid>[^,]+).*run_id=(?<rid>[^,]+)'
|
|
114
|
+
| stats count() by tid, rid, level
|
|
115
|
+
| where count > 100
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Advanced PPL Query Patterns:**
|
|
119
|
+
|
|
120
|
+
**Top N Analysis with Filtering:**
|
|
121
|
+
```ppl
|
|
122
|
+
source=ai-agent-logs-*
|
|
123
|
+
| where timestamp >= now() - 1h
|
|
124
|
+
| top 20 message by level
|
|
125
|
+
| where level in ["ERROR", "WARN"]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Deduplication and Unique Values:**
|
|
129
|
+
```ppl
|
|
130
|
+
source=ai-agent-audit-logs-*
|
|
131
|
+
| dedup thread_id
|
|
132
|
+
| fields thread_id, run_id, timestamp
|
|
133
|
+
| sort - timestamp
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Fillnull for Missing Data Handling:**
|
|
137
|
+
```ppl
|
|
138
|
+
source=ai-agent-metrics-*
|
|
139
|
+
| fillnull with 0 in cpu_usage, memory_usage
|
|
140
|
+
| stats avg(cpu_usage) as avg_cpu, avg(memory_usage) as avg_mem by host
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Rare Events Detection:**
|
|
144
|
+
```ppl
|
|
145
|
+
source=ai-agent-logs-*
|
|
146
|
+
| rare 10 error_code
|
|
147
|
+
| where count < 5
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Field Extraction with Grok:**
|
|
151
|
+
```ppl
|
|
152
|
+
source=ai-agent-logs-*
|
|
153
|
+
| grok message '%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} %{GREEDYDATA:msg}'
|
|
154
|
+
| stats count() by level
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Time Span Aggregations:**
|
|
158
|
+
```ppl
|
|
159
|
+
source=ai-agent-metrics-*
|
|
160
|
+
| stats count() by span(timestamp, 5m) as time_bucket, status
|
|
161
|
+
| where status != 200
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Eval with Conditional Logic:**
|
|
165
|
+
```ppl
|
|
166
|
+
source=ai-agent-logs-*
|
|
167
|
+
| eval severity = case(
|
|
168
|
+
level = "ERROR", 1,
|
|
169
|
+
level = "WARN", 2,
|
|
170
|
+
level = "INFO", 3,
|
|
171
|
+
else = 4
|
|
172
|
+
)
|
|
173
|
+
| stats count() by severity
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Join Operations (with Calcite enabled):**
|
|
177
|
+
```ppl
|
|
178
|
+
source=ai-agent-logs-*
|
|
179
|
+
| join left=l right=r on l.thread_id = r.thread_id
|
|
180
|
+
[ source=ai-agent-audit-logs-* ]
|
|
181
|
+
| fields l.timestamp, l.message, r.tool_name
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Subquery for Complex Filtering:**
|
|
185
|
+
```ppl
|
|
186
|
+
source=ai-agent-logs-*
|
|
187
|
+
| where thread_id in [
|
|
188
|
+
source=ai-agent-audit-logs-*
|
|
189
|
+
| where tool_name = "opensearch__search"
|
|
190
|
+
| fields thread_id
|
|
191
|
+
]
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Trendline for Moving Averages:**
|
|
195
|
+
```ppl
|
|
196
|
+
source=ai-agent-metrics-*
|
|
197
|
+
| trendline SMA(5, cpu_usage) as cpu_trend
|
|
198
|
+
| fields timestamp, cpu_usage, cpu_trend
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### PPL Best Practices
|
|
202
|
+
|
|
203
|
+
1. **Index Patterns**: Use wildcards for daily indices: `source=ai-agent-logs-*`
|
|
204
|
+
2. **Field Extraction**: Use `parse` for structured logs, `patterns` for unstructured
|
|
205
|
+
3. **Performance**: Apply `where` filters early in the pipeline
|
|
206
|
+
4. **Aggregations**: Use `stats` before `sort` for better performance
|
|
207
|
+
5. **Null Handling**: Use `fillnull` to handle missing data in calculations
|
|
208
|
+
|
|
209
|
+
### OpenSearch Index Patterns (Current Environment)
|
|
210
|
+
- `ai-agent-logs-YYYY.MM.DD` - Application logs
|
|
211
|
+
- `ai-agent-audit-logs-YYYY.MM.DD` - Audit logs
|
|
212
|
+
- `ai-agent-metrics-YYYY.MM.DD` - Prometheus metrics
|
|
213
|
+
|
|
214
|
+
## Query Response Formatting
|
|
215
|
+
You MUST respond with queries in the following format. `ppl` contains the valid ppl query
|
|
216
|
+
```typescript
|
|
217
|
+
query: {
|
|
218
|
+
ppl: string,
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## More PPL Queries
|
|
223
|
+
{% include "opensearch_ppl_query_docs.jinja2" %}
|
holmes/plugins/toolsets/git.py
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
import logging
|
|
3
|
-
import requests # type: ignore
|
|
4
3
|
import os
|
|
5
|
-
from typing import Any,
|
|
4
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
5
|
+
|
|
6
|
+
import requests # type: ignore
|
|
6
7
|
from pydantic import BaseModel
|
|
7
|
-
from holmes.core.tools import StructuredToolResult, ToolResultStatus
|
|
8
8
|
|
|
9
9
|
from holmes.core.tools import (
|
|
10
|
-
|
|
10
|
+
CallablePrerequisite,
|
|
11
|
+
StructuredToolResult,
|
|
12
|
+
StructuredToolResultStatus,
|
|
11
13
|
Tool,
|
|
14
|
+
ToolInvokeContext,
|
|
12
15
|
ToolParameter,
|
|
16
|
+
Toolset,
|
|
13
17
|
ToolsetTag,
|
|
14
|
-
CallablePrerequisite,
|
|
15
18
|
)
|
|
16
19
|
from holmes.plugins.toolsets.utils import toolset_name_for_one_liner
|
|
17
20
|
|
|
@@ -20,10 +23,12 @@ class GitHubConfig(BaseModel):
|
|
|
20
23
|
git_repo: str
|
|
21
24
|
git_credentials: str
|
|
22
25
|
git_branch: str = "main"
|
|
26
|
+
git_url: str = "https://api.github.com"
|
|
23
27
|
|
|
24
28
|
|
|
25
29
|
class GitToolset(Toolset):
|
|
26
30
|
git_repo: Optional[str] = None
|
|
31
|
+
git_url: Optional[str] = None
|
|
27
32
|
git_credentials: Optional[str] = None
|
|
28
33
|
git_branch: Optional[str] = None
|
|
29
34
|
_created_branches: set[str] = set() # Track branches created by the tool
|
|
@@ -33,7 +38,7 @@ class GitToolset(Toolset):
|
|
|
33
38
|
super().__init__(
|
|
34
39
|
name="git",
|
|
35
40
|
description="Runs git commands to read repos and create PRs",
|
|
36
|
-
docs_url="https://
|
|
41
|
+
docs_url="https://holmesgpt.dev/data-sources/builtin-toolsets/github/",
|
|
37
42
|
icon_url="https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg",
|
|
38
43
|
prerequisites=[CallablePrerequisite(callable=self.prerequisites_callable)],
|
|
39
44
|
tools=[
|
|
@@ -75,6 +80,7 @@ class GitToolset(Toolset):
|
|
|
75
80
|
|
|
76
81
|
try:
|
|
77
82
|
self.git_repo = os.getenv("GIT_REPO") or config.get("git_repo")
|
|
83
|
+
self.git_url = os.getenv("GIT_URL") or config.get("git_url")
|
|
78
84
|
self.git_credentials = os.getenv("GIT_CREDENTIALS") or config.get(
|
|
79
85
|
"git_credentials"
|
|
80
86
|
)
|
|
@@ -82,7 +88,9 @@ class GitToolset(Toolset):
|
|
|
82
88
|
"git_branch", "main"
|
|
83
89
|
)
|
|
84
90
|
|
|
85
|
-
if not all(
|
|
91
|
+
if not all(
|
|
92
|
+
[self.git_repo, self.git_url, self.git_credentials, self.git_branch]
|
|
93
|
+
):
|
|
86
94
|
logging.error("Missing one or more required Git configuration values.")
|
|
87
95
|
return False, "Missing one or more required Git configuration values."
|
|
88
96
|
return True, ""
|
|
@@ -96,7 +104,7 @@ class GitToolset(Toolset):
|
|
|
96
104
|
def list_open_prs(self) -> List[Dict[str, Any]]:
|
|
97
105
|
"""Helper method to list all open PRs in the repository."""
|
|
98
106
|
headers = {"Authorization": f"token {self.git_credentials}"}
|
|
99
|
-
url = f"
|
|
107
|
+
url = f"{self.git_url}/repos/{self.git_repo}/pulls?state=open"
|
|
100
108
|
resp = requests.get(url, headers=headers)
|
|
101
109
|
if resp.status_code != 200:
|
|
102
110
|
raise Exception(self._sanitize_error(f"Error listing PRs: {resp.text}"))
|
|
@@ -105,9 +113,7 @@ class GitToolset(Toolset):
|
|
|
105
113
|
def get_branch_ref(self, branch_name: str) -> Optional[str]:
|
|
106
114
|
"""Get the SHA of a branch reference."""
|
|
107
115
|
headers = {"Authorization": f"token {self.git_credentials}"}
|
|
108
|
-
url =
|
|
109
|
-
f"https://api.github.com/repos/{self.git_repo}/git/refs/heads/{branch_name}"
|
|
110
|
-
)
|
|
116
|
+
url = f"{self.git_url}/repos/{self.git_repo}/git/refs/heads/{branch_name}"
|
|
111
117
|
resp = requests.get(url, headers=headers)
|
|
112
118
|
if resp.status_code == 404:
|
|
113
119
|
return None
|
|
@@ -120,7 +126,7 @@ class GitToolset(Toolset):
|
|
|
120
126
|
def create_branch(self, branch_name: str, base_sha: str) -> None:
|
|
121
127
|
"""Create a new branch from a base SHA."""
|
|
122
128
|
headers = {"Authorization": f"token {self.git_credentials}"}
|
|
123
|
-
url = f"
|
|
129
|
+
url = f"{self.git_url}/repos/{self.git_repo}/git/refs"
|
|
124
130
|
resp = requests.post(
|
|
125
131
|
url,
|
|
126
132
|
headers=headers,
|
|
@@ -136,7 +142,7 @@ class GitToolset(Toolset):
|
|
|
136
142
|
def get_file_content(self, filepath: str, branch: str) -> tuple[str, str]:
|
|
137
143
|
"""Get file content and SHA from a specific branch."""
|
|
138
144
|
headers = {"Authorization": f"token {self.git_credentials}"}
|
|
139
|
-
url = f"
|
|
145
|
+
url = f"{self.git_url}/repos/{self.git_repo}/contents/{filepath}?ref={branch}"
|
|
140
146
|
resp = requests.get(url, headers=headers)
|
|
141
147
|
if resp.status_code == 404:
|
|
142
148
|
raise Exception(f"File not found: {filepath}")
|
|
@@ -150,7 +156,7 @@ class GitToolset(Toolset):
|
|
|
150
156
|
) -> None:
|
|
151
157
|
"""Update a file in a specific branch."""
|
|
152
158
|
headers = {"Authorization": f"token {self.git_credentials}"}
|
|
153
|
-
url = f"
|
|
159
|
+
url = f"{self.git_url}/repos/{self.git_repo}/contents/{filepath}"
|
|
154
160
|
encoded_content = base64.b64encode(content.encode()).decode()
|
|
155
161
|
resp = requests.put(
|
|
156
162
|
url,
|
|
@@ -168,7 +174,7 @@ class GitToolset(Toolset):
|
|
|
168
174
|
def create_pr(self, title: str, head: str, base: str, body: str) -> str:
|
|
169
175
|
"""Create a new pull request."""
|
|
170
176
|
headers = {"Authorization": f"token {self.git_credentials}"}
|
|
171
|
-
url = f"
|
|
177
|
+
url = f"{self.git_url}/repos/{self.git_repo}/pulls"
|
|
172
178
|
resp = requests.post(
|
|
173
179
|
url,
|
|
174
180
|
headers=headers,
|
|
@@ -188,7 +194,7 @@ class GitToolset(Toolset):
|
|
|
188
194
|
def get_pr_details(self, pr_number: int) -> Dict[str, Any]:
|
|
189
195
|
"""Get details of a specific PR."""
|
|
190
196
|
headers = {"Authorization": f"token {self.git_credentials}"}
|
|
191
|
-
url = f"
|
|
197
|
+
url = f"{self.git_url}/repos/{self.git_repo}/pulls/{pr_number}"
|
|
192
198
|
resp = requests.get(url, headers=headers)
|
|
193
199
|
if resp.status_code != 200:
|
|
194
200
|
raise Exception(
|
|
@@ -215,7 +221,7 @@ class GitToolset(Toolset):
|
|
|
215
221
|
|
|
216
222
|
# Update file
|
|
217
223
|
headers = {"Authorization": f"token {self.git_credentials}"}
|
|
218
|
-
url = f"
|
|
224
|
+
url = f"{self.git_url}/repos/{self.git_repo}/contents/{filepath}"
|
|
219
225
|
encoded_content = base64.b64encode(content.encode()).decode()
|
|
220
226
|
data = {
|
|
221
227
|
"message": message,
|
|
@@ -250,16 +256,18 @@ class GitReadFileWithLineNumbers(Tool):
|
|
|
250
256
|
)
|
|
251
257
|
|
|
252
258
|
def _invoke(
|
|
253
|
-
self,
|
|
259
|
+
self,
|
|
260
|
+
params: dict,
|
|
261
|
+
context: ToolInvokeContext,
|
|
254
262
|
) -> StructuredToolResult:
|
|
255
263
|
filepath = params["filepath"]
|
|
256
264
|
try:
|
|
257
265
|
headers = {"Authorization": f"token {self.toolset.git_credentials}"}
|
|
258
|
-
url = f"
|
|
266
|
+
url = f"{self.toolset.git_url}/repos/{self.toolset.git_repo}/contents/{filepath}"
|
|
259
267
|
resp = requests.get(url, headers=headers)
|
|
260
268
|
if resp.status_code != 200:
|
|
261
269
|
return StructuredToolResult(
|
|
262
|
-
status=
|
|
270
|
+
status=StructuredToolResultStatus.ERROR,
|
|
263
271
|
data=self.toolset._sanitize_error(
|
|
264
272
|
f"Error fetching file: {resp.text}"
|
|
265
273
|
),
|
|
@@ -268,13 +276,13 @@ class GitReadFileWithLineNumbers(Tool):
|
|
|
268
276
|
content = base64.b64decode(resp.json()["content"]).decode().splitlines()
|
|
269
277
|
numbered = "\n".join(f"{i+1}: {line}" for i, line in enumerate(content))
|
|
270
278
|
return StructuredToolResult(
|
|
271
|
-
status=
|
|
279
|
+
status=StructuredToolResultStatus.SUCCESS,
|
|
272
280
|
data=numbered,
|
|
273
281
|
params=params,
|
|
274
282
|
)
|
|
275
283
|
except Exception as e:
|
|
276
284
|
return StructuredToolResult(
|
|
277
|
-
status=
|
|
285
|
+
status=StructuredToolResultStatus.ERROR,
|
|
278
286
|
data=self.toolset._sanitize_error(str(e)),
|
|
279
287
|
params=params,
|
|
280
288
|
)
|
|
@@ -296,15 +304,17 @@ class GitListFiles(Tool):
|
|
|
296
304
|
)
|
|
297
305
|
|
|
298
306
|
def _invoke(
|
|
299
|
-
self,
|
|
307
|
+
self,
|
|
308
|
+
params: dict,
|
|
309
|
+
context: ToolInvokeContext,
|
|
300
310
|
) -> StructuredToolResult:
|
|
301
311
|
try:
|
|
302
312
|
headers = {"Authorization": f"token {self.toolset.git_credentials}"}
|
|
303
|
-
url = f"
|
|
313
|
+
url = f"{self.toolset.git_url}/repos/{self.toolset.git_repo}/git/trees/{self.toolset.git_branch}?recursive=1"
|
|
304
314
|
resp = requests.get(url, headers=headers)
|
|
305
315
|
if resp.status_code != 200:
|
|
306
316
|
return StructuredToolResult(
|
|
307
|
-
status=
|
|
317
|
+
status=StructuredToolResultStatus.ERROR,
|
|
308
318
|
data=self.toolset._sanitize_error(
|
|
309
319
|
f"Error listing files: {resp.text}"
|
|
310
320
|
),
|
|
@@ -312,13 +322,13 @@ class GitListFiles(Tool):
|
|
|
312
322
|
)
|
|
313
323
|
paths = [entry["path"] for entry in resp.json()["tree"]]
|
|
314
324
|
return StructuredToolResult(
|
|
315
|
-
status=
|
|
325
|
+
status=StructuredToolResultStatus.SUCCESS,
|
|
316
326
|
data=paths,
|
|
317
327
|
params=params,
|
|
318
328
|
)
|
|
319
329
|
except Exception as e:
|
|
320
330
|
return StructuredToolResult(
|
|
321
|
-
status=
|
|
331
|
+
status=StructuredToolResultStatus.ERROR,
|
|
322
332
|
data=self.toolset._sanitize_error(str(e)),
|
|
323
333
|
params=params,
|
|
324
334
|
)
|
|
@@ -338,9 +348,7 @@ class GitListOpenPRs(Tool):
|
|
|
338
348
|
toolset=toolset, # type: ignore
|
|
339
349
|
)
|
|
340
350
|
|
|
341
|
-
def _invoke(
|
|
342
|
-
self, params: dict, user_approved: bool = False
|
|
343
|
-
) -> StructuredToolResult:
|
|
351
|
+
def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
|
|
344
352
|
try:
|
|
345
353
|
prs = self.toolset.list_open_prs()
|
|
346
354
|
formatted = [
|
|
@@ -353,13 +361,13 @@ class GitListOpenPRs(Tool):
|
|
|
353
361
|
for pr in prs
|
|
354
362
|
]
|
|
355
363
|
return StructuredToolResult(
|
|
356
|
-
status=
|
|
364
|
+
status=StructuredToolResultStatus.SUCCESS,
|
|
357
365
|
data=formatted,
|
|
358
366
|
params=params,
|
|
359
367
|
)
|
|
360
368
|
except Exception as e:
|
|
361
369
|
return StructuredToolResult(
|
|
362
|
-
status=
|
|
370
|
+
status=StructuredToolResultStatus.ERROR,
|
|
363
371
|
data=self.toolset._sanitize_error(str(e)),
|
|
364
372
|
params=params,
|
|
365
373
|
)
|
|
@@ -408,19 +416,17 @@ class GitExecuteChanges(Tool):
|
|
|
408
416
|
toolset=toolset, # type: ignore
|
|
409
417
|
)
|
|
410
418
|
|
|
411
|
-
def _invoke(
|
|
412
|
-
self, params: dict, user_approved: bool = False
|
|
413
|
-
) -> StructuredToolResult:
|
|
419
|
+
def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
|
|
414
420
|
def error(msg: str) -> StructuredToolResult:
|
|
415
421
|
return StructuredToolResult(
|
|
416
|
-
status=
|
|
422
|
+
status=StructuredToolResultStatus.ERROR,
|
|
417
423
|
data=self.toolset._sanitize_error(msg),
|
|
418
424
|
params=params,
|
|
419
425
|
)
|
|
420
426
|
|
|
421
427
|
def success(msg: Any) -> StructuredToolResult:
|
|
422
428
|
return StructuredToolResult(
|
|
423
|
-
status=
|
|
429
|
+
status=StructuredToolResultStatus.SUCCESS, data=msg, params=params
|
|
424
430
|
)
|
|
425
431
|
|
|
426
432
|
def modify_lines(lines: List[str]) -> List[str]:
|
|
@@ -628,9 +634,7 @@ class GitUpdatePR(Tool):
|
|
|
628
634
|
toolset=toolset, # type: ignore
|
|
629
635
|
)
|
|
630
636
|
|
|
631
|
-
def _invoke(
|
|
632
|
-
self, params: dict, user_approved: bool = False
|
|
633
|
-
) -> StructuredToolResult:
|
|
637
|
+
def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
|
|
634
638
|
try:
|
|
635
639
|
line = params["line"]
|
|
636
640
|
filename = params["filename"]
|
|
@@ -643,24 +647,24 @@ class GitUpdatePR(Tool):
|
|
|
643
647
|
# Validate inputs
|
|
644
648
|
if not commit_message.strip():
|
|
645
649
|
return StructuredToolResult(
|
|
646
|
-
status=
|
|
650
|
+
status=StructuredToolResultStatus.ERROR,
|
|
647
651
|
error="Tool call failed to run: Commit message cannot be empty",
|
|
648
652
|
)
|
|
649
653
|
if not filename.strip():
|
|
650
654
|
return StructuredToolResult(
|
|
651
|
-
status=
|
|
655
|
+
status=StructuredToolResultStatus.ERROR,
|
|
652
656
|
error="Tool call failed to run: Filename cannot be empty",
|
|
653
657
|
)
|
|
654
658
|
if line < 1:
|
|
655
659
|
return StructuredToolResult(
|
|
656
|
-
status=
|
|
660
|
+
status=StructuredToolResultStatus.ERROR,
|
|
657
661
|
error="Tool call failed to run: Line number must be positive",
|
|
658
662
|
)
|
|
659
663
|
|
|
660
664
|
# Verify this is a PR created by our tool
|
|
661
665
|
if not self.toolset.is_created_pr(pr_number):
|
|
662
666
|
return StructuredToolResult(
|
|
663
|
-
status=
|
|
667
|
+
status=StructuredToolResultStatus.ERROR,
|
|
664
668
|
error=f"Tool call failed to run: PR #{pr_number} was not created by this tool. Only PRs created using git_execute_changes can be updated.",
|
|
665
669
|
)
|
|
666
670
|
|
|
@@ -714,7 +718,7 @@ class GitUpdatePR(Tool):
|
|
|
714
718
|
del content_lines[line - 1]
|
|
715
719
|
else:
|
|
716
720
|
return StructuredToolResult(
|
|
717
|
-
status=
|
|
721
|
+
status=StructuredToolResultStatus.ERROR,
|
|
718
722
|
error=f"Tool call failed to run: Invalid command: {command}",
|
|
719
723
|
)
|
|
720
724
|
|
|
@@ -722,7 +726,7 @@ class GitUpdatePR(Tool):
|
|
|
722
726
|
|
|
723
727
|
if dry_run:
|
|
724
728
|
return StructuredToolResult(
|
|
725
|
-
status=
|
|
729
|
+
status=StructuredToolResultStatus.SUCCESS,
|
|
726
730
|
data=f"DRY RUN: Updated content for PR #{pr_number}:\n\n{updated_content}",
|
|
727
731
|
)
|
|
728
732
|
|
|
@@ -731,13 +735,13 @@ class GitUpdatePR(Tool):
|
|
|
731
735
|
pr_number, filename, updated_content, commit_message
|
|
732
736
|
)
|
|
733
737
|
return StructuredToolResult(
|
|
734
|
-
status=
|
|
738
|
+
status=StructuredToolResultStatus.SUCCESS,
|
|
735
739
|
data=f"Added commit to PR #{pr_number} successfully",
|
|
736
740
|
)
|
|
737
741
|
|
|
738
742
|
except Exception as e:
|
|
739
743
|
return StructuredToolResult(
|
|
740
|
-
status=
|
|
744
|
+
status=StructuredToolResultStatus.ERROR,
|
|
741
745
|
error=self.toolset._sanitize_error(
|
|
742
746
|
f"Tool call failed to run: Error updating PR: {str(e)}"
|
|
743
747
|
),
|
|
@@ -745,14 +749,14 @@ class GitUpdatePR(Tool):
|
|
|
745
749
|
|
|
746
750
|
except requests.exceptions.RequestException as e:
|
|
747
751
|
return StructuredToolResult(
|
|
748
|
-
status=
|
|
752
|
+
status=StructuredToolResultStatus.ERROR,
|
|
749
753
|
error=self.toolset._sanitize_error(
|
|
750
754
|
f"Tool call failed to run: Network error: {str(e)}"
|
|
751
755
|
),
|
|
752
756
|
)
|
|
753
757
|
except Exception as e:
|
|
754
758
|
return StructuredToolResult(
|
|
755
|
-
status=
|
|
759
|
+
status=StructuredToolResultStatus.ERROR,
|
|
756
760
|
error=self.toolset._sanitize_error(
|
|
757
761
|
f"Tool call failed to run: Unexpected error: {str(e)}"
|
|
758
762
|
),
|