How to create KB article by using Now Assist

dkishore090
Tera Contributor

Hi All,

When P1 or P2 incidents gets resolved automatically KB article should be created By using custom prompt from Business rule in Zurich.

Kindly share some suggestions/idea.

We have created some BR to achieve this, unlucky this BR not working .
Kindly share with your suggestions/inputs on this.

Table : Incident
Condition : after 
State changed To Resolved 
Priority is P1 or P2

(function executeRule(current, previous) {
  try {

    // ----------------------------------------
    // 1. PREVENT DUPLICATE KB CREATION
    // ----------------------------------------
    if (!gs.nil(current.u_kb_article)) {
      gs.info('[KB Auto-Gen] KB already exists for: ' + current.getValue('number'));
      return;
    }

    // ----------------------------------------
    // 2. FETCH FRESH INCIDENT VIA GLIDERECORD
    // ----------------------------------------
    var sysId = current.getValue('sys_id');
    var incGR = new GlideRecord('incident');
    incGR.addQuery('sys_id', sysId);
    incGR.query();

    if (!incGR.next()) {
      gs.error('[KB Auto-Gen] GlideRecord fetch failed for sys_id: ' + sysId);
      return;
    }

    gs.info('[KB Auto-Gen] Fetched incident: ' + incGR.getValue('number'));

    // ----------------------------------------
    // 3. BUILD PROMPT INPUT PAYLOAD
    // ----------------------------------------
    var promptInput = {
      number:            incGR.getValue('number')            || '',
      priority:          incGR.priority.getDisplayValue()    || '',
      short_description: incGR.getValue('short_description') || '',
      description:       incGR.getValue('description')       || '',
      close_notes:       incGR.getValue('close_notes')       || '',
      resolved_at:       incGR.resolved_at.getDisplayValue() || '',
      cmdb_ci:           incGR.cmdb_ci.nil()          ? '' : incGR.cmdb_ci.getDisplayValue(),
      assignment_group:  incGR.assignment_group.nil() ? '' : incGR.assignment_group.getDisplayValue(),
      resolved_by:       incGR.resolved_by.nil()      ? '' : incGR.resolved_by.getDisplayValue()
    };

    gs.info('[KB Auto-Gen] Prompt input: ' + JSON.stringify(promptInput));

    // ----------------------------------------
    // 4. LOOK UP NOW ASSIST PROMPT BY NAME
    // ----------------------------------------
    var promptGR = new GlideRecord('sys_hub_action_type_definition');
    promptGR.addQuery('name', 'KB Generation P1 Change Incident');
    promptGR.setLimit(1);
    promptGR.query();

    if (!promptGR.next()) {
      gs.error('[KB Auto-Gen] Now Assist Prompt not found: "KB Generation P1 Change Incident"');
      return;
    }

    var capabilityId = promptGR.getValue('sys_id');
    gs.info('[KB Auto-Gen] Prompt sys_id (capabilityId): ' + capabilityId);

    // ----------------------------------------
    // 5. CALL NOW ASSIST SKILL
    // ----------------------------------------
    var request = {
      executionRequests: [{
        payload: promptInput,
        capabilityId: capabilityId
      }],
      mode: 'sync'
    };

    var response = sn_one_extend.OneExtendUtil.execute(request);
    gs.info('[KB Auto-Gen] Raw skill response: ' + JSON.stringify(response));

    // ----------------------------------------
    // 6. VALIDATE RESPONSE
    // ----------------------------------------
    if (!response || !response.capabilities || !response.capabilities[capabilityId]) {
      gs.error('[KB Auto-Gen] Invalid or missing capability in response for: ' + incGR.getValue('number'));
      return;
    }

    var aiResponse = response.capabilities[capabilityId].response;
    gs.info('[KB Auto-Gen] AI response: ' + aiResponse);

    if (gs.nil(aiResponse)) {
      gs.error('[KB Auto-Gen] Empty AI response for: ' + incGR.getValue('number'));
      return;
    }

    // ----------------------------------------
    // 7. PARSE AI RESPONSE (handles JSON wrap)
    // ----------------------------------------
    var kbText = aiResponse;
    try {
      var parsed = JSON.parse(aiResponse);
      if (parsed && parsed.model_output) {
        kbText = parsed.model_output;
      }
    } catch (parseErr) {
      // Plain text response — use as-is
    }

    gs.info('[KB Auto-Gen] KB text (first 300): ' + kbText.substring(0, 300));

    // ----------------------------------------
    // 8. CREATE KB ARTICLE
    // ----------------------------------------
    var kb = new GlideRecord('kb_knowledge');
    kb.initialize();
    kb.setValue('short_description', 'P1 RCA - ' + promptInput.short_description);
    kb.setValue('text',              kbText);
    kb.setValue('kb_knowledge_base', 'c7190f9d87384710158b62083cbb3583'); // your KB base sys_id
    kb.setValue('workflow_state',    'draft');
    kb.setValue('author',            gs.getUserID());

    var kbSysId = kb.insert();

    if (!kbSysId) {
      gs.error('[KB Auto-Gen] KB insert failed for: ' + promptInput.number +
               ' — check ACLs and mandatory fields');
      return;
    }

    gs.info('[KB Auto-Gen] KB article created: ' + kbSysId);

    // ----------------------------------------
    // 9. UPDATE INCIDENT WORK NOTES
    // ----------------------------------------
    var kbGR = new GlideRecord('kb_knowledge');
    if (kbGR.get(kbSysId)) {
      current.work_notes =
        'KB Article Auto-Generated:\n' +
        'KB Number: '       + kbGR.getValue('number')            + '\n' +
        'Title: '           + kbGR.getValue('short_description') + '\n' +
        'State: '           + kbGR.getValue('workflow_state')    + '\n' +
        'Link: /kb_view.do?sys_kb_id=' + kbSysId;
    }

    current.setWorkflow(false);
    current.update();

    gs.info('[KB Auto-Gen] Incident updated. Done.');

  } catch (ex) {
    gs.error('[KB Auto-Gen] Exception: ' + ex.message + ' | Stack: ' + ex.stack);
  }


 

6 REPLIES 6

abdulrehmanArif
Tera Expert

 

Spoiler

Hey @dkishore090, The core issue here is actually the approach rather than the script itself — let me explain what's wrong and what ServiceNow recommends.

The main problem: synchronous Business Rule + Now Assist don't mix well

Calling sn_one_extend.OneExtendUtil.execute() in sync mode from a Business Rule is unreliable. Business Rules run in the transaction pipeline, and a synchronous LLM call in that context can timeout, get blocked, or silently fail — which is likely why your BR "isn't working." ServiceNow's own NASK documentation always shows this pattern being used from a UI Action, not a server-side BR.

The right approach for your use case:

  1. Keep your BR lightweight — just fire an event:
 
 
javascript
if (current.state.changesTo('6') && (current.priority == '1' || current.priority == '2')) {
    gs.eventQueue('incident.p1p2.resolved', current, current.sys_id, current.number);
}
  1. Handle the actual KB creation in a Script Action or Flow Designer flow triggered by that event. This runs asynchronously, outside the BR transaction, and won't block or timeout.
  2. In the Script Action, use sn_one_extend.OneExtendUtil.execute() with the correct payload format — pass inputs as { tableName, sysId, queryString } (not raw field values), and reference both capabilityId and skillConfigId in your request. Your current script passes a flat JSON object as the payload which isn't the correct structure NASK expects.

Also check:

  • Your prompt is looked up against sys_hub_action_type_definition, but the correct table for NASK skills is actually the capability record — verify the sys_id you're using is accurate in your environment.
  • Make sure the Now Assist for ITSM (sn_itsm_gen_ai) plugin is active and your LLM connection is healthy — test the skill directly in the Now Assist Skill Kit UI first before calling it programmatically.
  • The KB insert needs a valid kb_knowledge_base sys_id and any mandatory fields your KB configuration enforces, otherwise kb.insert() returns null silently.

The async event → Script Action pattern is the ServiceNow-recommended way to do background automation that involves Now Assist. Once you move the LLM call out of the BR transaction, you'll find it works reliably.

 

sunkarasath
Tera Contributor
I'm working on similar client requirements and creating custom skills. As per my knowledge, I'll share a few things.
I just saw your BR script, it is good and modular, but use the UI action script which is given by the skill.
How to get that script?
 After skill definition, go to skill settings >> deployment options >> enable UI action, and go to that record. Copy the script in that UI action and paste that in your BR  this script is much cleaner. It directly triggers the skill and returns the response. You need to parse and use the response
for that, use strict JSON output from the skill.
Here are a few points you need to check:
1. After pasting, it returns a skill response which follows a specific JSON structure. Observe it using the gs.addInfoMessage() function. Normally, double parsing is needed. And one more thing, write the prompt so it only returns the strict JSON object without any noise text or preamble.
2. Finally, check these: your prompt must be finalized, the skill must be published, and the skill must be activated through the Now Assist admin console. Check the BR working with conditions.
please accept the solution if helpful