Introduction

An AI agent can generate search queries for any record type from natural language.

In this example we'll use Anthropic's Claude Haiku 3.5 model. You can use any language model from popular providers.

When dealing with personally identifying information (PII) you should use a model with strong privacy protection. Amazon Bedrock runs Anthropic Claude within an AWS region, or you can use a locally hosted model with tools like Ollama or Docker Model Runner.

Installation

Connect to an Anthropic account

Create an Anthropic connected account in Cerb.

Import the example workflow

Create a new workflow from Search » Workflows » (+) » Empty.

Copy and paste the following workflow template:

workflow:
  name: wgm.agent.search
  description: An intelligent search agent in the search menu.
  version: 2025-06-04T00:55:51Z
  requirements:
    cerb_plugins: cerberusweb.core, 
    cerb_version: >=11.1.2 <11.2
  config:
    chooser/anthropic_account_id:
      label: Anthropic Account:
      record_type: connected_account
      record_query: anthropic
records:
  automation/interaction_search:
    fields:
      name: wgm.agent.search
      extension_id: cerb.trigger.interaction.worker
      description@text:
      script@raw:
        inputs:
          text/query:
            type: freeform
            description: The natural language query describing a desired search query.
            #required@bool: yes
        
        start:
          set/config:
            config@json: {{cerb_workflow_config('wgm.agent.search')|json_encode}} 
            
          outcome/noQuery:
            if@bool: {{inputs.query is empty}}
            then:
              await:
                form:
                  title: Search
                  elements:
                    text/prompt_query:
                      label: What would you like to search for?
                      placeholder: Open tickets from Fiaflux so far this year
                      required@bool: yes
                      type: freeform
        
          # Load all possible record types
          data.query/record_types:
            output: results
            inputs:
              query@text:
                type:record.types
                format:dictionaries
            on_success:
              set:
                prompt_record_types@text:
                  {{results.data|column('uri')|sort|join(', ')}}
          
          # Cascade the search query
          set/init:
            search_query: {{inputs.query|default(prompt_query)}}
          
          llm.agent:
            output: results
            inputs:
              llm:
                anthropic:
                  #model: claude-sonnet-4-20250514
                  model: claude-3-5-haiku-20241022
                  authentication: cerb:connected_account:{{config.anthropic_account_id}}
                  max_tokens@int: 2048
              system_prompt@text:
                You are a search agent within that lives within Cerb helpdesk software.
                
                You are talking to a user named {{worker_first_name}} (ID: {{worker_id}}).
                The current date/time is: {{'now'|date('r')}}
        
                Your job is to return search query syntax matching natural language queries for a given record type.
                
                Cerb query syntax uses the space-delimited format `filterName:expression otherFilter:expression`.
                
                ALWAYS double quote string expressions (especially with spaces).
                
                ALWAYS handle multiple-value expressions with square brackets ([]) and not the 'OR' operator. `expression:[value1,"value2 with spaces"]`
                
                Date filters use relative expressions ("-1 year") or absolute ranges ("2025-01-01 to now").
                
                YOU MUST use one of these record types:
                {{prompt_record_types}}
                
                Use the `get_record_types` tool to get filters by record type.
                
                ALWAYS check record filters. Don't guess.
                
                If the user asks for "my" they always mean either "owner:me" or "owner.worker:me" (check record type filters).
                
                You MAY search into linked `virtual` filters with parenthesis. For instance `org:(country:"United States")` on `ticket` records.
                ALWAYS use the `get_record_types` tool to see what filters you can use for the linked record type.
                
                Use your tools to build and test queries.
                
                Always use the `return_query` tool to return just the query. Then you're done.
                
                <examples>
                org:(country:["Germany","United States"]) created:"big bang to -2 years" owner:me
                </examples>
                
              messages:
                message:
                  role: user
                  content: {{search_query}}
              tools:
                #automation/get_record_types:
                #  uri: cerb:automation:wgm.agent.search.tool.getRecordTypes
                automation/get_record_filters:
                  uri: cerb:automation:wgm.agent.search.tool.getRecordFilters
                automation/test_search_query:
                  uri: cerb:automation:wgm.agent.search.tool.testSearchQuery
                tool/return_query:
                  description: Use this tool to return the generated search query.
                  parameters:
                    string/record_type:
                      description: The record type to search.
                      required@bool: yes
                    string/search_query:
                      description: The generated search query without extra formatting or commentary.
                      required@bool: yes
            on_tool:
              decision/tool:
                outcome/return_query:
                  if@bool: {{__tool.name == 'return_query'}}
                  then:
                    return:
                      search:
                        record_type: {{__tool.parameters.record_type}}
                        query: {{__tool.parameters.search_query}}
                      data:
                        query: {{__tool.parameters.search_query}}
        
                outcome/else:
                  then:
                    await:
                      form:
                        title: Search Agent
                        elements:
                          llmTranscript/prompt_transcript:
                            session_id: {{results.session_id}}
                            tool_labels:
                              get_record_filters@raw:
                                Looking up filters for {{record_type}} records
                              test_search_query@raw:
                                Verifying the {{record_type}} query:
                                {{record_query}}
                              return_query@raw:
                                Returning the query:
                                {{query}}
                          submit:
                            is_automatic@bool: yes
          
      policy_kata@raw:
        commands:
          data.query:
            deny/type@bool: {{query.type != 'record.types'}}
            allow@bool: yes
          llm.agent:
            allow@bool: yes
  automation/tool_get_record_types:
    fields:
      name: wgm.agent.search.tool.getRecordTypes
      extension_id: cerb.trigger.llm.tool
      description: Return a list of record type names and IDs
      script@raw:
        start:
          data.query/getTypes:
            output: results
            inputs:
              query@text:
                type:record.types
                limit:1000
                exclude_custom:no
                options:[search]
                format:dictionaries
          return:
            content@text:
              These record types are available:
              
              {% for record_type in results.data|sort((a,b) => a.uri <=> b.uri) %}
              {{record_type.uri}}: {{record_type.label_plural}}
              {% endfor %}
      policy_kata@raw:
        commands:
          data.query:
            allow@bool: yes
  automation/tool_get_record_filters:
    fields:
      name: wgm.agent.search.tool.getRecordFilters
      extension_id: cerb.trigger.llm.tool
      description: Return a list of filters for a given record type
      script@raw:
        inputs:
          text/record_type:
            type: record_type
            description: The ID of the record type (e.g. `ticket`)
            required@bool: yes
        
        start:
          data.query/getFields:
            output: results
            inputs:
              query@text:
                type:record.filters
                of:{{inputs.record_type}}
                exclude_links:yes
                format:dictionaries
          return:
            content@text:
              These filters are available on `{{inputs.record_type}}` records:
              
              {% for filter_key, filter_data in results.data %}
              ---
              filter: {{filter_key}}
              type: {{filter_data.type}}
              {% if filter_data.examples and filter_data.examples|first is not iterable %}
              examples:
              {{filter_data.examples|join('\n')|indent('* ')}}
              {% elseif filter_data.examples and filter_data.examples[0].type == 'list' %}
              examples:
              {{filter_data.examples[0].values|kata_encode|indent('* ')}}
              {% endif %}
              
              {% endfor %}
      policy_kata@raw:
        commands:
          data.query:
            allow@bool: yes
  automation/tool_test_search_query:
    fields:
      name: wgm.agent.search.tool.testSearchQuery
      extension_id: cerb.trigger.llm.tool
      description@text:
      script@raw:
        inputs:
          text/record_type:
            type: record_type
            description: The record type to search for
            required@bool: yes
          text/record_query:
            type: freeform
            description: The search query to test
            required@bool: yes
        
        start:
          data.query/search:
            output: results
            inputs:
              query@text:
                type:worklist.records
                of:{{inputs.record_type}}
                query:({{inputs.record_query}})
                format:dictionaries
            on_success:
              return:
                content: SUCCESS: Found {{results._.paging.page.rows.of}} records
            on_error:
              return:
                content: ERROR: {{results.error}}
      policy_kata@raw:
        commands:
          data.query:
            deny/type@bool: {{query.type != 'worklist.records'}}
            allow@bool: yes
  toolbar_section/toolbar_search:
    fields:
      name: Search Agent
      toolbar_name: global.search
      priority@int: 0
      is_disabled: 0
      toolbar_kata@raw:
        interaction/c9bghh:
          label: Search with AI
          uri: cerb:automation:wgm.agent.search
          icon: magic
          #hidden@bool: {{row_selections ? 'no' : 'yes'}}
          #badge: 0
          #after:
            #refresh_widgets@list: Actions

Click the Continue button.

Link your Anthropic account and click Continue again.

The following records will be created:

Usage

From Search, select "Search with AI".

Type what you're looking for in natural language and the search agent will construct the query for you and display the results: