Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Obs AI Assistant] Fix chat on the Alerts page #197126

Merged
merged 7 commits into from
Oct 30, 2024

Conversation

viduni94
Copy link
Contributor

@viduni94 viduni94 commented Oct 21, 2024

Closes #184214

Summary

Problem

The Observability AI Assistant doesn't work on the Alerts page - errors out with a 400 status code from OpenAI.
The reason for this is that the description for the function get_data_on_screen is too long, and there is a token limit for function descriptions by OpenAI.

Note: This error does not occur with Gemini or Bedrock because simulated function calling is enabled by default for them. With simulated function calling, all functions and their descriptions are appended to the system message, therefore doesn't run into a token limit error as opposed to OpenAI.

Solution

Append the function description to the system message instead of sending it with the function.

The implementation includes:

  • Registering an AdHoc instruction
  • Retrieving AdHoc instructions
  • Combine the retrieved AdHoc instructions with the other adHoc instructions passed to the chat and pass all AdHoc instructions to the getSystemMessageFromInstructions function.

This correctly orders the description for the get_data_on_screen function at the end of the system message

@viduni94 viduni94 added release_note:fix v9.0.0 backport:prev-minor Backport to (8.x) the previous minor version (i.e. one version back from main) labels Oct 21, 2024
@viduni94 viduni94 self-assigned this Oct 21, 2024
@viduni94 viduni94 requested a review from a team as a code owner October 21, 2024 19:16
@elasticmachine
Copy link
Contributor

Pinging @elastic/obs-ai-assistant (Team:Obs AI Assistant)

Copy link
Contributor

🤖 GitHub comments

Expand to view the GitHub comments

Just comment with:

  • /oblt-deploy : Deploy a Kibana instance using the Observability test environments.
  • run docs-build : Re-trigger the docs validation. (use unformatted text in the comment!)

@@ -123,7 +124,10 @@ export const createOpenAiAdapter: LlmApiAdapterFactory = ({
...(!!functionsForOpenAI?.length
? {
tools: functionsForOpenAI.map((fn) => ({
function: pick(fn, 'name', 'description', 'parameters'),
function:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can go for a different fix that doesn't special-case the get_data_on_screen function. Instead of using the function description, we can add an instruction in the same place where we are registering the get_data_on_screen function:

.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. Thanks @dgieselaar
I'm looking into it.

Copy link
Contributor Author

@viduni94 viduni94 Oct 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @dgieselaar

I implemented the above change as follows:

if (allData.length) {
      this.registerFunction(
        {
          name: GET_DATA_ON_SCREEN_FUNCTION_NAME,
          description: 'Get data that is on the screen',
          visibility: FunctionVisibility.AssistantOnly,
          parameters: {....},
        async ({ arguments: { data: dataNames } }) => {
          return {
            content: allData.filter((data) => dataNames.includes(data.name)),
          };
        },
        ['all']
      );

      const instruction: RegisterInstructionCallback = ({ availableFunctionNames }) =>
        availableFunctionNames.includes(GET_DATA_ON_SCREEN_FUNCTION_NAME)
          ? `The ${GET_DATA_ON_SCREEN_FUNCTION_NAME} function will ${dedent(`Get data that is on the screen:
            ${allData.map((data) => `${data.name}: ${data.description}`).join('\n')}
          `)}`
          : undefined;

      this.registerInstruction({ instruction, scopes: ['all'] });
    }

However, when testing I noticed that,

  • When I register the instruction at the time the function is being registered, the instruction is appended to the top of the prompt, as follows:
{
      role: 'system',
      content: 'The get_data_on_screen function will Get data that is on the screen:\n' +
        '.es-query: An available rule is Elasticsearch query.\n' +
        'observability.rules.custom_threshold: An available rule is Custom threshold.\n' +
        'xpack.ml.anomaly_detection_alert: An available rule is Anomaly detection.\n' +
        'slo.rules.burnRate: An available rule is SLO burn rate.\n' +
        'metrics.alert.threshold: An available rule is Metric threshold.\n' +
        'metrics.alert.inventory.threshold: An available rule is Inventory.\n' +
        'logs.alert.document.count: An available rule is Log threshold.\n' +
        'xpack.uptime.alerts.tlsCertificate: An available rule is Uptime TLS.\n' +
        'xpack.uptime.alerts.monitorStatus: An available rule is Uptime monitor status.\n' +
        'xpack.uptime.alerts.durationAnomaly: An available rule is Uptime Duration Anomaly.\n' +
        'xpack.synthetics.alerts.monitorStatus: An available rule is Synthetics monitor status.\n' +
        'xpack.synthetics.alerts.tls: An available rule is Synthetics TLS certificate.\n' +
        'apm.error_rate: An available rule is Error count threshold.\n' +
        'apm.transaction_error_rate: An available rule is Failed transaction rate threshold.\n' +
        'apm.transaction_duration: An available rule is Latency threshold.\n' +
        'apm.anomaly: An available rule is APM Anomaly.\n' +
        '\n' +
        'You are a helpful assistant for Elastic Observability. Your goal is to help the Elastic Observability users to quickly assess what is happening in their observed systems. You can help them visualise and analyze data, investigate their systems, perform root cause analysis or identify optimisation opportunities.\n' +
        '\n' +
        "  It's very important to not assume what the user is meaning. Ask them for clarification if needed.\n" +
        '\n' +
        '  If you are unsure about which function should be used and with what arguments, ask the user for clarification or confirmation........',
      function_call: undefined,
      name: undefined
    }

This is probably because the system instructions are registered afterwards.. do you have any thoughts on this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, @sphilipse you also have a requirement to treat certain instructions differently, correct? is that about ordering or some other mechanism?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah that was about the way we use scopes to filter out some things in some cases, but I think that's resolved with the work we're doing in #196322.

We already have ways to fix ordering, right? System instructions are added to the top, but there are separate instructions that are added lower. You just need to not register them as an AdHocInstruction (see: https://github.com/elastic/kibana/blob/main/x-pack/plugins/observability_solution/observability_ai_assistant/common/types.ts and https://github.com/elastic/kibana/blob/main/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/util/get_system_message_from_instructions.ts)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @sphilipse
I've updated the PR with the implementation to register adhoc instructions.

With this implementation, I am able to register the description of the get_data_on_screen function as an adhoc instruction and order it at the end of the system message as specified by the getSystemMessageFromInstructions function.

cc: @dgieselaar

@viduni94 viduni94 force-pushed the chat-doesnt-work-on-the-alert-page branch 5 times, most recently from 620a7ee to b19f5ab Compare October 28, 2024 13:34
@viduni94 viduni94 requested review from dgieselaar and a team October 28, 2024 19:09
viduni94 added a commit to viduni94/kibana that referenced this pull request Oct 30, 2024
@viduni94 viduni94 force-pushed the chat-doesnt-work-on-the-alert-page branch from f1530f0 to e3af6ec Compare October 30, 2024 13:10
@elasticmachine
Copy link
Contributor

elasticmachine commented Oct 30, 2024

💚 Build Succeeded

  • Buildkite Build
  • Commit: e3af6ec
  • Kibana Serverless Image: docker.elastic.co/kibana-ci/kibana-serverless:pr-197126-e3af6ec01a85

Metrics [docs]

✅ unchanged

History

cc @viduni94

@viduni94 viduni94 merged commit 354bec4 into elastic:main Oct 30, 2024
25 checks passed
@kibanamachine
Copy link
Contributor

Starting backport for target branches: 8.x

https://github.com/elastic/kibana/actions/runs/11597841867

kibanamachine pushed a commit to kibanamachine/kibana that referenced this pull request Oct 30, 2024
Closes elastic#184214

## Summary

### Problem
The Observability AI Assistant doesn't work on the Alerts page - errors
out with a 400 status code from OpenAI.
The reason for this is that the description for the function
`get_data_on_screen` is too long, and there is a token limit for
function descriptions by OpenAI.

**Note:** This error does not occur with Gemini or Bedrock because
simulated function calling is enabled by default for them. With
simulated function calling, all functions and their descriptions are
appended to the system message, therefore doesn't run into a token limit
error as opposed to OpenAI.

### Solution
Append the function description to the system message instead of sending
it with the function.

The implementation includes:
- Registering an AdHoc instruction
- Retrieving AdHoc instructions
- Combine the retrieved AdHoc instructions with the other adHoc
instructions passed to the chat and pass all AdHoc instructions to the
`getSystemMessageFromInstructions` function.

This correctly orders the description for the `get_data_on_screen`
function at the end of the system message

_OpenAI request object **before** the above update:_
<details>
  <summary>Click to expand JSON</summary>

```json
{
  "messages": [
    {
      "role": "system",
      "content": "You are a helpful assistant for Elastic Observability. Your goal is to help the Elastic Observability users to quickly assess what is happening in their observed systems. You can help them visualise and analyze data, investigate their systems, perform root cause analysis or identify optimisation opportunities.\n\n  It's very important to not assume what the user is meaning. Ask them for clarification if needed.\n\n  If you are unsure about which function should be used and with what arguments, ask the user for clarification or confirmation.\n\n  In KQL (\"kqlFilter\")) escaping happens with double quotes, not single quotes. Some characters that need escaping are: ':()\\  /\". Always put a field value in double quotes. Best: service.name:\"opbeans-go\". Wrong: service.name:opbeans-go. This is very important!\n\n  You can use Github-flavored Markdown in your responses. If a function returns an array, consider using a Markdown table to format the response.\n\n  Note that ES|QL (the Elasticsearch Query Language which is a new piped language) is the preferred query language.\n\n  If you want to call a function or tool, only call it a single time per message. Wait until the function has been executed and its results\n  returned to you, before executing the same tool or another tool again if needed.\n\n  DO NOT UNDER ANY CIRCUMSTANCES USE ES|QL syntax (`service.name == \"foo\"`) with \"kqlFilter\" (`service.name:\"foo\"`).\n\n  The user is able to change the language which they want you to reply in on the settings page of the AI Assistant for Observability, which can be found in the Stack Management app under the option AI Assistants.\n  If the user asks how to change the language, reply in the same language the user asked in.\n\nYou MUST use the \"query\" function when the user wants to:\n  - visualize data\n  - run any arbitrary query\n  - breakdown or filter ES|QL queries that are displayed on the current page\n  - convert queries from another language to ES|QL\n  - asks general questions about ES|QL\n\n  DO NOT UNDER ANY CIRCUMSTANCES generate ES|QL queries or explain anything about the ES|QL query language yourself.\n  DO NOT UNDER ANY CIRCUMSTANCES try to correct an ES|QL query yourself - always use the \"query\" function for this.\n\n  If the user asks for a query, and one of the dataset info functions was called and returned no results, you should still call the query function to generate an example query.\n\n  Even if the \"query\" function was used before that, follow it up with the \"query\" function. If a query fails, do not attempt to correct it yourself. Again you should call the \"query\" function,\n  even if it has been called before.\n\n  When the \"visualize_query\" function has been called, a visualization has been displayed to the user. DO NOT UNDER ANY CIRCUMSTANCES follow up a \"visualize_query\" function call with your own visualization attempt.\n  If the \"execute_query\" function has been called, summarize these results for the user. The user does not see a visualization in this case.\n\nYou MUST use the \"get_dataset_info\"  function before calling the \"query\" or the \"changes\" functions.\n\nIf a function requires an index, you MUST use the results from the dataset info functions.\n\nYou have access to data on the screen by calling the \"get_data_on_screen\" function.\nUse it to help the user understand what they are looking at. A short summary of what they are looking at is available in the return of the \"context\" function.\nData that is compact enough automatically gets included in the response for the \"context\" function.\n\nYou can use the \"summarize\" function to store new information you have learned in a knowledge database.\nOnly use this function when the user asks for it.\nAll summaries MUST be created in English, even if the conversation was carried out in a different language."
    },
    { "role": "user", "content": "Can you explain this page?" },
    { "role": "assistant", "content": "", "function_call": { "name": "context", "arguments": "{}" } },
    {
      "role": "user",
      "content": "{\"screen_description\":\"The user is looking at http://localhost:5601/phq/app/observability/alerts?_a=(filters:!(),kuery:%27%27,rangeFrom:now-24h,rangeTo:now,status:all). The current time range is 2024-10-21T18:23:54.539Z - 2024-10-21T18:38:54.539Z.\",\"learnings\":[],\"data_on_screen\":[{\"name\":\".es-query\",\"value\":\"Elasticsearch query Alert when matches are found during the latest query run.\",\"description\":\"An available rule is Elasticsearch query.\"},{\"name\":\"observability.rules.custom_threshold\",\"value\":\"Custom threshold Alert when any Observability data type reaches or exceeds a given value.\",\"description\":\"An available rule is Custom threshold.\"},{\"name\":\"xpack.ml.anomaly_detection_alert\",\"value\":\"Anomaly detection Alert when anomaly detection jobs results match the condition.\",\"description\":\"An available rule is Anomaly detection.\"},{\"name\":\"slo.rules.burnRate\",\"value\":\"SLO burn rate Alert when your SLO burn rate is too high over a defined period of time.\",\"description\":\"An available rule is SLO burn rate.\"},{\"name\":\"metrics.alert.threshold\",\"value\":\"Metric threshold Alert when the metrics aggregation exceeds the threshold.\",\"description\":\"An available rule is Metric threshold.\"},{\"name\":\"metrics.alert.inventory.threshold\",\"value\":\"Inventory Alert when the inventory exceeds a defined threshold.\",\"description\":\"An available rule is Inventory.\"},{\"name\":\"logs.alert.document.count\",\"value\":\"Log threshold Alert when the log aggregation exceeds the threshold.\",\"description\":\"An available rule is Log threshold.\"},{\"name\":\"xpack.uptime.alerts.tlsCertificate\",\"value\":\"Uptime TLS Alert when the TLS certificate of an Uptime monitor is about to expire.\",\"description\":\"An available rule is Uptime TLS.\"},{\"name\":\"xpack.uptime.alerts.monitorStatus\",\"value\":\"Uptime monitor status Alert when a monitor is down or an availability threshold is breached.\",\"description\":\"An available rule is Uptime monitor status.\"},{\"name\":\"xpack.uptime.alerts.durationAnomaly\",\"value\":\"Uptime Duration Anomaly Alert when the Uptime monitor duration is anomalous.\",\"description\":\"An available rule is Uptime Duration Anomaly.\"},{\"name\":\"xpack.synthetics.alerts.monitorStatus\",\"value\":\"Synthetics monitor status Alert when a monitor is down.\",\"description\":\"An available rule is Synthetics monitor status.\"},{\"name\":\"xpack.synthetics.alerts.tls\",\"value\":\"Synthetics TLS certificate Alert when the TLS certificate of a Synthetics monitor is about to expire.\",\"description\":\"An available rule is Synthetics TLS certificate.\"},{\"name\":\"apm.error_rate\",\"value\":\"Error count threshold Alert when the number of errors in a service exceeds a defined threshold.\",\"description\":\"An available rule is Error count threshold.\"},{\"name\":\"apm.transaction_error_rate\",\"value\":\"Failed transaction rate threshold Alert when the rate of transaction errors in a service exceeds a defined threshold.\",\"description\":\"An available rule is Failed transaction rate threshold.\"},{\"name\":\"apm.transaction_duration\",\"value\":\"Latency threshold Alert when the latency of a specific transaction type in a service exceeds a defined threshold.\",\"description\":\"An available rule is Latency threshold.\"},{\"name\":\"apm.anomaly\",\"value\":\"APM Anomaly Alert when either the latency, throughput, or failed transaction rate of a service is anomalous.\",\"description\":\"An available rule is APM Anomaly.\"}]}",
      "name": "context"
    }
  ],
  "stream": true,
  "tools": [
    {
      "function": {
        "name": "get_data_on_screen",
        "description": "Get data that is on the screen:\n.es-query: An available rule is Elasticsearch query.\nobservability.rules.custom_threshold: An available rule is Custom threshold.\nxpack.ml.anomaly_detection_alert: An available rule is Anomaly detection.\nslo.rules.burnRate: An available rule is SLO burn rate.\nmetrics.alert.threshold: An available rule is Metric threshold.\nmetrics.alert.inventory.threshold: An available rule is Inventory.\nlogs.alert.document.count: An available rule is Log threshold.\nxpack.uptime.alerts.tlsCertificate: An available rule is Uptime TLS.\nxpack.uptime.alerts.monitorStatus: An available rule is Uptime monitor status.\nxpack.uptime.alerts.durationAnomaly: An available rule is Uptime Duration Anomaly.\nxpack.synthetics.alerts.monitorStatus: An available rule is Synthetics monitor status.\nxpack.synthetics.alerts.tls: An available rule is Synthetics TLS certificate.\napm.error_rate: An available rule is Error count threshold.\napm.transaction_error_rate: An available rule is Failed transaction rate threshold.\napm.transaction_duration: An available rule is Latency threshold.\napm.anomaly: An available rule is APM Anomaly.",
        "parameters": {
          "type": "object",
          "properties": {
            "data": {
              "type": "array",
              "description": "The pieces of data you want to look at it. You can request one, or multiple",
              "items": {
                "type": "string",
                "enum": [
                  ".es-query",
                  "observability.rules.custom_threshold",
                  "xpack.ml.anomaly_detection_alert",
                  "slo.rules.burnRate",
                  "metrics.alert.threshold",
                  "metrics.alert.inventory.threshold",
                  "logs.alert.document.count",
                  "xpack.uptime.alerts.tlsCertificate",
                  "xpack.uptime.alerts.monitorStatus",
                  "xpack.uptime.alerts.durationAnomaly",
                  "xpack.synthetics.alerts.monitorStatus",
                  "xpack.synthetics.alerts.tls",
                  "apm.error_rate",
                  "apm.transaction_error_rate",
                  "apm.transaction_duration",
                  "apm.anomaly"
                ]
              }
            }
          },
          "required": ["data"]
        }
      },
      "type": "function"
    },
    {
      "function": {
        "name": "query",
        "description": "This function generates, executes and/or visualizes a query\n      based on the user's request. It also explains how ES|QL works and how to\n      convert queries from one language to another. Make sure you call one of\n      the get_dataset functions first if you need index or field names. This\n      function takes no input.",
        "parameters": { "type": "object", "properties": {} }
      },
      "type": "function"
    },
    {
      "function": {
        "name": "get_alerts_dataset_info",
        "description": "Use this function to get information about alerts data.",
        "parameters": {
          "type": "object",
          "properties": {
            "start": {
              "type": "string",
              "description": "The start of the current time range, in datemath, like now-24h or an ISO timestamp"
            },
            "end": {
              "type": "string",
              "description": "The end of the current time range, in datemath, like now-24h or an ISO timestamp"
            }
          }
        }
      },
      "type": "function"
    },
    {
      "function": {
        "name": "alerts",
        "description": "Get alerts for Observability.  Make sure get_alerts_dataset_info was called before.\n        Use this to get open (and optionally recovered) alerts for Observability assets, like services,\n        hosts or containers.\n        Display the response in tabular format if appropriate.\n      ",
        "parameters": {
          "type": "object",
          "properties": {
            "start": {
              "type": "string",
              "description": "The start of the time range, in Elasticsearch date math, like `now`."
            },
            "end": {
              "type": "string",
              "description": "The end of the time range, in Elasticsearch date math, like `now-24h`."
            },
            "kqlFilter": { "type": "string", "description": "Filter alerts by field:value pairs" },
            "includeRecovered": {
              "type": "boolean",
              "description": "Whether to include recovered/closed alerts. Defaults to false, which means only active alerts will be returned"
            }
          },
          "required": ["start", "end"]
        }
      },
      "type": "function"
    },
    {
      "function": {
        "name": "changes",
        "description": "Returns change points like spikes and dips for logs and metrics.",
        "parameters": {
          "type": "object",
          "properties": {
            "start": {
              "type": "string",
              "description": "The beginning of the time range, in datemath, like now-24h, or an ISO timestamp"
            },
            "end": {
              "type": "string",
              "description": "The end of the time range, in datemath, like now, or an ISO timestamp"
            },
            "logs": {
              "description": "Analyze changes in log patterns. If no index is given, the default logs index pattern will be used",
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "name": { "type": "string", "description": "The name of this set of logs" },
                  "index": { "type": "string", "description": "The index or index pattern where to find the logs" },
                  "kqlFilter": {
                    "type": "string",
                    "description": "A KQL filter to filter the log documents by, e.g. my_field:foo"
                  },
                  "field": {
                    "type": "string",
                    "description": "The text field that contains the message to be analyzed, usually `message`. ONLY use field names from the conversation."
                  }
                },
                "required": ["name"]
              }
            },
            "metrics": {
              "description": "Analyze changes in metrics. DO NOT UNDER ANY CIRCUMSTANCES use date or metric fields for groupBy, leave empty unless needed.",
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "name": { "type": "string", "description": "The name of this set of metrics" },
                  "index": { "type": "string", "description": "The index or index pattern where to find the metrics" },
                  "kqlFilter": {
                    "type": "string",
                    "description": "A KQL filter to filter the log documents by, e.g. my_field:foo"
                  },
                  "field": {
                    "type": "string",
                    "description": "Metric field that contains the metric. Only use if the metric aggregation type is not count."
                  },
                  "type": {
                    "type": "string",
                    "description": "The type of metric aggregation to perform. Defaults to count",
                    "enum": ["count", "avg", "sum", "min", "max", "p95", "p99"]
                  },
                  "groupBy": {
                    "type": "array",
                    "description": "Optional keyword fields to group metrics by.",
                    "items": { "type": "string" }
                  }
                },
                "required": ["index", "name"]
              }
            }
          },
          "required": ["start", "end"]
        }
      },
      "type": "function"
    },
    {
      "function": {
        "name": "summarize",
        "description": "Use this function to store facts in the knowledge database if the user requests it.\n        You can score the learnings with a confidence metric, whether it is a correction on a previous learning.\n        An embedding will be created that you can recall later with a semantic search.\n        When you create this summarisation, make sure you craft it in a way that can be recalled with a semantic\n        search later, and that it would have answered the user's original request.",
        "parameters": {
          "type": "object",
          "properties": {
            "id": {
              "type": "string",
              "description": "An id for the document. This should be a short human-readable keyword field with only alphabetic characters and underscores, that allow you to update it later."
            },
            "text": {
              "type": "string",
              "description": "A human-readable summary of what you have learned, described in such a way that you can recall it later with semantic search, and that it would have answered the user's original request."
            },
            "is_correction": {
              "type": "boolean",
              "description": "Whether this is a correction for a previous learning."
            },
            "confidence": {
              "type": "string",
              "description": "How confident you are about this being a correct and useful learning",
              "enum": ["low", "medium", "high"]
            },
            "public": {
              "type": "boolean",
              "description": "Whether this information is specific to the user, or generally applicable to any user of the product"
            }
          },
          "required": ["id", "text", "is_correction", "confidence", "public"]
        }
      },
      "type": "function"
    },
    {
      "function": {
        "name": "elasticsearch",
        "description": "Call Elasticsearch APIs on behalf of the user. Make sure the request body is valid for the API that you are using. Only call this function when the user has explicitly requested it.",
        "parameters": {
          "type": "object",
          "properties": {
            "method": {
              "type": "string",
              "description": "The HTTP method of the Elasticsearch endpoint",
              "enum": ["GET", "PUT", "POST", "DELETE", "PATCH"]
            },
            "path": {
              "type": "string",
              "description": "The path of the Elasticsearch endpoint, including query parameters"
            },
            "body": { "type": "object", "description": "The body of the request" }
          },
          "required": ["method", "path"]
        }
      },
      "type": "function"
    },
    {
      "function": {
        "name": "kibana",
        "description": "Call Kibana APIs on behalf of the user. Only call this function when the user has explicitly requested it, and you know how to call it, for example by querying the knowledge base or having the user explain it to you. Assume that pathnames, bodies and query parameters may have changed since your knowledge cut off date.",
        "parameters": {
          "type": "object",
          "properties": {
            "method": {
              "type": "string",
              "description": "The HTTP method of the Kibana endpoint",
              "enum": ["GET", "PUT", "POST", "DELETE", "PATCH"]
            },
            "pathname": {
              "type": "string",
              "description": "The pathname of the Kibana endpoint, excluding query parameters"
            },
            "query": { "type": "object", "description": "The query parameters, as an object" },
            "body": { "type": "object", "description": "The body of the request" }
          },
          "required": ["method", "pathname"]
        }
      },
      "type": "function"
    },
    {
      "function": {
        "name": "get_dataset_info",
        "description": "Use this function to get information about indices/datasets available and the fields available on them.\n\n      providing empty string as index name will retrieve all indices\n      else list of all fields for the given index will be given. if no fields are returned this means no indices were matched by provided index pattern.\n      wildcards can be part of index name.",
        "parameters": {
          "type": "object",
          "properties": {
            "index": {
              "type": "string",
              "description": "index pattern the user is interested in or empty string to get information about all available indices"
            }
          },
          "required": ["index"]
        }
      },
      "type": "function"
    },
    {
      "function": {
        "name": "execute_connector",
        "description": "Use this function when user explicitly asks to call a kibana connector.",
        "parameters": {
          "type": "object",
          "properties": {
            "id": { "type": "string", "description": "The id of the connector" },
            "params": { "type": "object", "description": "The connector parameters" }
          },
          "required": ["id", "params"]
        }
      },
      "type": "function"
    }
  ],
  "temperature": 0
}
```
</details>

_OpenAI request object **after** the above update:_
<details>
  <summary>Click to expand JSON</summary>

```json
{
  "messages": [
    {
      "role": "system",
      "content": "You are a helpful assistant for Elastic Observability. Your goal is to help the Elastic Observability users to quickly assess what is happening in their observed systems. You can help them visualise and analyze data, investigate their systems, perform root cause analysis or identify optimisation opportunities.\n\n  It's very important to not assume what the user is meaning. Ask them for clarification if needed.\n\n  If you are unsure about which function should be used and with what arguments, ask the user for clarification or confirmation.\n\n  In KQL (\"kqlFilter\")) escaping happens with double quotes, not single quotes. Some characters that need escaping are: ':()\\  /\". Always put a field value in double quotes. Best: service.name:\"opbeans-go\". Wrong: service.name:opbeans-go. This is very important!\n\n  You can use Github-flavored Markdown in your responses. If a function returns an array, consider using a Markdown table to format the response.\n\n  Note that ES|QL (the Elasticsearch Query Language which is a new piped language) is the preferred query language.\n\n  If you want to call a function or tool, only call it a single time per message. Wait until the function has been executed and its results\n  returned to you, before executing the same tool or another tool again if needed.\n\n  DO NOT UNDER ANY CIRCUMSTANCES USE ES|QL syntax (`service.name == \"foo\"`) with \"kqlFilter\" (`service.name:\"foo\"`).\n\n  The user is able to change the language which they want you to reply in on the settings page of the AI Assistant for Observability, which can be found in the Stack Management app under the option AI Assistants.\n  If the user asks how to change the language, reply in the same language the user asked in.\n\nYou MUST use the \"query\" function when the user wants to:\n  - visualize data\n  - run any arbitrary query\n  - breakdown or filter ES|QL queries that are displayed on the current page\n  - convert queries from another language to ES|QL\n  - asks general questions about ES|QL\n\n  DO NOT UNDER ANY CIRCUMSTANCES generate ES|QL queries or explain anything about the ES|QL query language yourself.\n  DO NOT UNDER ANY CIRCUMSTANCES try to correct an ES|QL query yourself - always use the \"query\" function for this.\n\n  If the user asks for a query, and one of the dataset info functions was called and returned no results, you should still call the query function to generate an example query.\n\n  Even if the \"query\" function was used before that, follow it up with the \"query\" function. If a query fails, do not attempt to correct it yourself. Again you should call the \"query\" function,\n  even if it has been called before.\n\n  When the \"visualize_query\" function has been called, a visualization has been displayed to the user. DO NOT UNDER ANY CIRCUMSTANCES follow up a \"visualize_query\" function call with your own visualization attempt.\n  If the \"execute_query\" function has been called, summarize these results for the user. The user does not see a visualization in this case.\n\nYou MUST use the \"get_dataset_info\"  function before calling the \"query\" or the \"changes\" functions.\n\nIf a function requires an index, you MUST use the results from the dataset info functions.\n\nYou have access to data on the screen by calling the \"get_data_on_screen\" function.\nUse it to help the user understand what they are looking at. A short summary of what they are looking at is available in the return of the \"context\" function.\nData that is compact enough automatically gets included in the response for the \"context\" function.\n\nYou can use the \"summarize\" function to store new information you have learned in a knowledge database.\nOnly use this function when the user asks for it.\nAll summaries MUST be created in English, even if the conversation was carried out in a different language.\nThe \"get_data_on_screen\" function will Get data that is on the screen:\n.es-query: An available rule is Elasticsearch query.\nobservability.rules.custom_threshold: An available rule is Custom threshold.\nxpack.ml.anomaly_detection_alert: An available rule is Anomaly detection.\nslo.rules.burnRate: An available rule is SLO burn rate.\nmetrics.alert.threshold: An available rule is Metric threshold.\nmetrics.alert.inventory.threshold: An available rule is Inventory.\nlogs.alert.document.count: An available rule is Log threshold.\nxpack.uptime.alerts.tlsCertificate: An available rule is Uptime TLS.\nxpack.uptime.alerts.monitorStatus: An available rule is Uptime monitor status.\nxpack.uptime.alerts.durationAnomaly: An available rule is Uptime Duration Anomaly.\nxpack.synthetics.alerts.monitorStatus: An available rule is Synthetics monitor status.\nxpack.synthetics.alerts.tls: An available rule is Synthetics TLS certificate.\napm.error_rate: An available rule is Error count threshold.\napm.transaction_error_rate: An available rule is Failed transaction rate threshold.\napm.transaction_duration: An available rule is Latency threshold.\napm.anomaly: An available rule is APM Anomaly."
    },
    { "role": "user", "content": "Can you explain this page?" },
    { "role": "assistant", "content": "", "function_call": { "name": "context", "arguments": "{}" } },
    {
      "role": "user",
      "content": "{\"screen_description\":\"The user is looking at http://localhost:5601/phq/app/observability/alerts?_a=(filters:!(),kuery:%27%27,rangeFrom:now-24h,rangeTo:now,status:all). The current time range is 2024-10-21T18:23:54.539Z - 2024-10-21T18:38:54.539Z.\",\"learnings\":[],\"data_on_screen\":[{\"name\":\".es-query\",\"value\":\"Elasticsearch query Alert when matches are found during the latest query run.\",\"description\":\"An available rule is Elasticsearch query.\"},{\"name\":\"observability.rules.custom_threshold\",\"value\":\"Custom threshold Alert when any Observability data type reaches or exceeds a given value.\",\"description\":\"An available rule is Custom threshold.\"},{\"name\":\"xpack.ml.anomaly_detection_alert\",\"value\":\"Anomaly detection Alert when anomaly detection jobs results match the condition.\",\"description\":\"An available rule is Anomaly detection.\"},{\"name\":\"slo.rules.burnRate\",\"value\":\"SLO burn rate Alert when your SLO burn rate is too high over a defined period of time.\",\"description\":\"An available rule is SLO burn rate.\"},{\"name\":\"metrics.alert.threshold\",\"value\":\"Metric threshold Alert when the metrics aggregation exceeds the threshold.\",\"description\":\"An available rule is Metric threshold.\"},{\"name\":\"metrics.alert.inventory.threshold\",\"value\":\"Inventory Alert when the inventory exceeds a defined threshold.\",\"description\":\"An available rule is Inventory.\"},{\"name\":\"logs.alert.document.count\",\"value\":\"Log threshold Alert when the log aggregation exceeds the threshold.\",\"description\":\"An available rule is Log threshold.\"},{\"name\":\"xpack.uptime.alerts.tlsCertificate\",\"value\":\"Uptime TLS Alert when the TLS certificate of an Uptime monitor is about to expire.\",\"description\":\"An available rule is Uptime TLS.\"},{\"name\":\"xpack.uptime.alerts.monitorStatus\",\"value\":\"Uptime monitor status Alert when a monitor is down or an availability threshold is breached.\",\"description\":\"An available rule is Uptime monitor status.\"},{\"name\":\"xpack.uptime.alerts.durationAnomaly\",\"value\":\"Uptime Duration Anomaly Alert when the Uptime monitor duration is anomalous.\",\"description\":\"An available rule is Uptime Duration Anomaly.\"},{\"name\":\"xpack.synthetics.alerts.monitorStatus\",\"value\":\"Synthetics monitor status Alert when a monitor is down.\",\"description\":\"An available rule is Synthetics monitor status.\"},{\"name\":\"xpack.synthetics.alerts.tls\",\"value\":\"Synthetics TLS certificate Alert when the TLS certificate of a Synthetics monitor is about to expire.\",\"description\":\"An available rule is Synthetics TLS certificate.\"},{\"name\":\"apm.error_rate\",\"value\":\"Error count threshold Alert when the number of errors in a service exceeds a defined threshold.\",\"description\":\"An available rule is Error count threshold.\"},{\"name\":\"apm.transaction_error_rate\",\"value\":\"Failed transaction rate threshold Alert when the rate of transaction errors in a service exceeds a defined threshold.\",\"description\":\"An available rule is Failed transaction rate threshold.\"},{\"name\":\"apm.transaction_duration\",\"value\":\"Latency threshold Alert when the latency of a specific transaction type in a service exceeds a defined threshold.\",\"description\":\"An available rule is Latency threshold.\"},{\"name\":\"apm.anomaly\",\"value\":\"APM Anomaly Alert when either the latency, throughput, or failed transaction rate of a service is anomalous.\",\"description\":\"An available rule is APM Anomaly.\"}]}",
      "name": "context"
    }
  ],
  "stream": true,
  "tools": [
    {
      "function": {
        "name": "get_data_on_screen",
        "parameters": {
          "type": "object",
          "properties": {
            "data": {
              "type": "array",
              "description": "The pieces of data you want to look at it. You can request one, or multiple",
              "items": {
                "type": "string",
                "enum": [
                  ".es-query",
                  "observability.rules.custom_threshold",
                  "xpack.ml.anomaly_detection_alert",
                  "slo.rules.burnRate",
                  "metrics.alert.threshold",
                  "metrics.alert.inventory.threshold",
                  "logs.alert.document.count",
                  "xpack.uptime.alerts.tlsCertificate",
                  "xpack.uptime.alerts.monitorStatus",
                  "xpack.uptime.alerts.durationAnomaly",
                  "xpack.synthetics.alerts.monitorStatus",
                  "xpack.synthetics.alerts.tls",
                  "apm.error_rate",
                  "apm.transaction_error_rate",
                  "apm.transaction_duration",
                  "apm.anomaly"
                ]
              }
            }
          },
          "required": ["data"]
        }
      },
      "type": "function"
    },
    {
      "function": {
        "name": "query",
        "description": "This function generates, executes and/or visualizes a query\n      based on the user's request. It also explains how ES|QL works and how to\n      convert queries from one language to another. Make sure you call one of\n      the get_dataset functions first if you need index or field names. This\n      function takes no input.",
        "parameters": { "type": "object", "properties": {} }
      },
      "type": "function"
    },
    {
      "function": {
        "name": "get_alerts_dataset_info",
        "description": "Use this function to get information about alerts data.",
        "parameters": {
          "type": "object",
          "properties": {
            "start": {
              "type": "string",
              "description": "The start of the current time range, in datemath, like now-24h or an ISO timestamp"
            },
            "end": {
              "type": "string",
              "description": "The end of the current time range, in datemath, like now-24h or an ISO timestamp"
            }
          }
        }
      },
      "type": "function"
    },
    {
      "function": {
        "name": "alerts",
        "description": "Get alerts for Observability.  Make sure get_alerts_dataset_info was called before.\n        Use this to get open (and optionally recovered) alerts for Observability assets, like services,\n        hosts or containers.\n        Display the response in tabular format if appropriate.\n      ",
        "parameters": {
          "type": "object",
          "properties": {
            "start": {
              "type": "string",
              "description": "The start of the time range, in Elasticsearch date math, like `now`."
            },
            "end": {
              "type": "string",
              "description": "The end of the time range, in Elasticsearch date math, like `now-24h`."
            },
            "kqlFilter": { "type": "string", "description": "Filter alerts by field:value pairs" },
            "includeRecovered": {
              "type": "boolean",
              "description": "Whether to include recovered/closed alerts. Defaults to false, which means only active alerts will be returned"
            }
          },
          "required": ["start", "end"]
        }
      },
      "type": "function"
    },
    {
      "function": {
        "name": "changes",
        "description": "Returns change points like spikes and dips for logs and metrics.",
        "parameters": {
          "type": "object",
          "properties": {
            "start": {
              "type": "string",
              "description": "The beginning of the time range, in datemath, like now-24h, or an ISO timestamp"
            },
            "end": {
              "type": "string",
              "description": "The end of the time range, in datemath, like now, or an ISO timestamp"
            },
            "logs": {
              "description": "Analyze changes in log patterns. If no index is given, the default logs index pattern will be used",
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "name": { "type": "string", "description": "The name of this set of logs" },
                  "index": { "type": "string", "description": "The index or index pattern where to find the logs" },
                  "kqlFilter": {
                    "type": "string",
                    "description": "A KQL filter to filter the log documents by, e.g. my_field:foo"
                  },
                  "field": {
                    "type": "string",
                    "description": "The text field that contains the message to be analyzed, usually `message`. ONLY use field names from the conversation."
                  }
                },
                "required": ["name"]
              }
            },
            "metrics": {
              "description": "Analyze changes in metrics. DO NOT UNDER ANY CIRCUMSTANCES use date or metric fields for groupBy, leave empty unless needed.",
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "name": { "type": "string", "description": "The name of this set of metrics" },
                  "index": { "type": "string", "description": "The index or index pattern where to find the metrics" },
                  "kqlFilter": {
                    "type": "string",
                    "description": "A KQL filter to filter the log documents by, e.g. my_field:foo"
                  },
                  "field": {
                    "type": "string",
                    "description": "Metric field that contains the metric. Only use if the metric aggregation type is not count."
                  },
                  "type": {
                    "type": "string",
                    "description": "The type of metric aggregation to perform. Defaults to count",
                    "enum": ["count", "avg", "sum", "min", "max", "p95", "p99"]
                  },
                  "groupBy": {
                    "type": "array",
                    "description": "Optional keyword fields to group metrics by.",
                    "items": { "type": "string" }
                  }
                },
                "required": ["index", "name"]
              }
            }
          },
          "required": ["start", "end"]
        }
      },
      "type": "function"
    },
    {
      "function": {
        "name": "summarize",
        "description": "Use this function to store facts in the knowledge database if the user requests it.\n        You can score the learnings with a confidence metric, whether it is a correction on a previous learning.\n        An embedding will be created that you can recall later with a semantic search.\n        When you create this summarisation, make sure you craft it in a way that can be recalled with a semantic\n        search later, and that it would have answered the user's original request.",
        "parameters": {
          "type": "object",
          "properties": {
            "id": {
              "type": "string",
              "description": "An id for the document. This should be a short human-readable keyword field with only alphabetic characters and underscores, that allow you to update it later."
            },
            "text": {
              "type": "string",
              "description": "A human-readable summary of what you have learned, described in such a way that you can recall it later with semantic search, and that it would have answered the user's original request."
            },
            "is_correction": {
              "type": "boolean",
              "description": "Whether this is a correction for a previous learning."
            },
            "confidence": {
              "type": "string",
              "description": "How confident you are about this being a correct and useful learning",
              "enum": ["low", "medium", "high"]
            },
            "public": {
              "type": "boolean",
              "description": "Whether this information is specific to the user, or generally applicable to any user of the product"
            }
          },
          "required": ["id", "text", "is_correction", "confidence", "public"]
        }
      },
      "type": "function"
    },
    {
      "function": {
        "name": "elasticsearch",
        "description": "Call Elasticsearch APIs on behalf of the user. Make sure the request body is valid for the API that you are using. Only call this function when the user has explicitly requested it.",
        "parameters": {
          "type": "object",
          "properties": {
            "method": {
              "type": "string",
              "description": "The HTTP method of the Elasticsearch endpoint",
              "enum": ["GET", "PUT", "POST", "DELETE", "PATCH"]
            },
            "path": {
              "type": "string",
              "description": "The path of the Elasticsearch endpoint, including query parameters"
            },
            "body": { "type": "object", "description": "The body of the request" }
          },
          "required": ["method", "path"]
        }
      },
      "type": "function"
    },
    {
      "function": {
        "name": "kibana",
        "description": "Call Kibana APIs on behalf of the user. Only call this function when the user has explicitly requested it, and you know how to call it, for example by querying the knowledge base or having the user explain it to you. Assume that pathnames, bodies and query parameters may have changed since your knowledge cut off date.",
        "parameters": {
          "type": "object",
          "properties": {
            "method": {
              "type": "string",
              "description": "The HTTP method of the Kibana endpoint",
              "enum": ["GET", "PUT", "POST", "DELETE", "PATCH"]
            },
            "pathname": {
              "type": "string",
              "description": "The pathname of the Kibana endpoint, excluding query parameters"
            },
            "query": { "type": "object", "description": "The query parameters, as an object" },
            "body": { "type": "object", "description": "The body of the request" }
          },
          "required": ["method", "pathname"]
        }
      },
      "type": "function"
    },
    {
      "function": {
        "name": "get_dataset_info",
        "description": "Use this function to get information about indices/datasets available and the fields available on them.\n\n      providing empty string as index name will retrieve all indices\n      else list of all fields for the given index will be given. if no fields are returned this means no indices were matched by provided index pattern.\n      wildcards can be part of index name.",
        "parameters": {
          "type": "object",
          "properties": {
            "index": {
              "type": "string",
              "description": "index pattern the user is interested in or empty string to get information about all available indices"
            }
          },
          "required": ["index"]
        }
      },
      "type": "function"
    },
    {
      "function": {
        "name": "execute_connector",
        "description": "Use this function when user explicitly asks to call a kibana connector.",
        "parameters": {
          "type": "object",
          "properties": {
            "id": { "type": "string", "description": "The id of the connector" },
            "params": { "type": "object", "description": "The connector parameters" }
          },
          "required": ["id", "params"]
        }
      },
      "type": "function"
    }
  ],
  "temperature": 0
}
```
</details>

---------

Co-authored-by: Søren Louv-Jansen <[email protected]>
(cherry picked from commit 354bec4)
@kibanamachine
Copy link
Contributor

💔 All backports failed

Status Branch Result
8.x Could not create pull request: Validation Failed: {"resource":"Issue","code":"custom","field":"body","message":"body is too long (maximum is 65536 characters)"}

Manual backport

To create the backport manually run:

node scripts/backport --pr 197126

Questions ?

Please refer to the Backport tool documentation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport:prev-minor Backport to (8.x) the previous minor version (i.e. one version back from main) ci:project-deploy-observability Create an Observability project release_note:fix Team:Obs AI Assistant v9.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Observability AI Assistant] Chat doesn't work on alert page
6 participants