-
Notifications
You must be signed in to change notification settings - Fork 400
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Added new API method
withLlmCustomAttributes
to run a functio…
…n in a LLM context (#2437) The context will be used to assign custom attributes to every LLM event produced within the function
- Loading branch information
Showing
12 changed files
with
397 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,7 @@ const obfuscate = require('./lib/util/sql/obfuscate') | |
const { DESTINATIONS } = require('./lib/config/attribute-filter') | ||
const parse = require('module-details-from-path') | ||
const { isSimpleObject } = require('./lib/util/objects') | ||
const { AsyncLocalStorage } = require('async_hooks') | ||
|
||
/* | ||
* | ||
|
@@ -1902,4 +1903,52 @@ API.prototype.ignoreApdex = function ignoreApdex() { | |
transaction.ignoreApdex = true | ||
} | ||
|
||
/** | ||
Check warning on line 1906 in api.js GitHub Actions / lint (lts/*)
|
||
* Run a function with the passed in LLM context as the active context and return its return value. | ||
* | ||
* An example of setting a custom attribute: | ||
* | ||
* newrelic.withLlmCustomAttributes({'llm.someAttribute': 'someValue'}, () => { | ||
* return; | ||
* }) | ||
* @param {Object} context LLM custom attributes context | ||
* @param {Function} callback The function to execute in context. | ||
*/ | ||
API.prototype.withLlmCustomAttributes = function withLlmCustomAttributes(context, callback) { | ||
context = context || {} | ||
const metric = this.agent.metrics.getOrCreateMetric( | ||
NAMES.SUPPORTABILITY.API + '/withLlmCustomAttributes' | ||
) | ||
metric.incrementCallCount() | ||
|
||
const transaction = this.agent.tracer.getTransaction() | ||
|
||
if (!callback || typeof callback !== 'function') { | ||
logger.warn('withLlmCustomAttributes must be used with a valid callback') | ||
return | ||
} | ||
|
||
if (!transaction) { | ||
logger.warn('withLlmCustomAttributes must be called within the scope of a transaction.') | ||
return callback() | ||
} | ||
|
||
for (const [key, value] of Object.entries(context)) { | ||
if (typeof value === 'object' || typeof value === 'function') { | ||
logger.warn(`Invalid attribute type for ${key}. Skipped.`) | ||
delete context[key] | ||
} else if (key.indexOf('llm.') !== 0) { | ||
logger.warn(`Invalid attribute name ${key}. Renamed to "llm.${key}".`) | ||
delete context[key] | ||
context[`llm.${key}`] = value | ||
} | ||
} | ||
|
||
transaction._llmContextManager = transaction._llmContextManager || new AsyncLocalStorage() | ||
const parentContext = transaction._llmContextManager.getStore() || {} | ||
|
||
const fullContext = Object.assign({}, parentContext, context) | ||
return transaction._llmContextManager.run(fullContext, callback) | ||
} | ||
|
||
module.exports = API |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/* | ||
* Copyright 2020 New Relic Corporation. All rights reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
'use strict' | ||
|
||
exports = module.exports = { extractLlmContext, extractLlmAttributes } | ||
|
||
/** | ||
* Extract LLM attributes from the LLM context | ||
* | ||
* @param {Object} context LLM context object | ||
* @returns {Object} LLM custom attributes | ||
*/ | ||
function extractLlmAttributes(context) { | ||
return Object.keys(context).reduce((result, key) => { | ||
if (key.indexOf('llm.') === 0) { | ||
result[key] = context[key] | ||
} | ||
return result | ||
}, {}) | ||
} | ||
|
||
/** | ||
* Extract LLM context from the active transaction | ||
* | ||
* @param {Agent} agent NR agent instance | ||
* @returns {Object} LLM context object | ||
*/ | ||
function extractLlmContext(agent) { | ||
const context = agent.tracer.getTransaction()?._llmContextManager?.getStore() || {} | ||
return extractLlmAttributes(context) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/* | ||
* Copyright 2023 New Relic Corporation. All rights reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
'use strict' | ||
|
||
const tap = require('tap') | ||
const { extractLlmAttributes, extractLlmContext } = require('../../../lib/util/llm-utils') | ||
const { AsyncLocalStorage } = require('async_hooks') | ||
|
||
tap.test('extractLlmAttributes', (t) => { | ||
const context = { | ||
'skip': 1, | ||
'llm.get': 2, | ||
'fllm.skip': 3 | ||
} | ||
|
||
const llmContext = extractLlmAttributes(context) | ||
t.notOk(llmContext.skip) | ||
t.notOk(llmContext['fllm.skip']) | ||
t.equal(llmContext['llm.get'], 2) | ||
t.end() | ||
}) | ||
|
||
tap.test('extractLlmContext', (t) => { | ||
t.beforeEach((t) => { | ||
const tx = { | ||
_llmContextManager: new AsyncLocalStorage() | ||
} | ||
t.context.agent = { | ||
tracer: { | ||
getTransaction: () => { | ||
return tx | ||
} | ||
} | ||
} | ||
t.context.tx = tx | ||
}) | ||
|
||
t.test('handle empty context', (t) => { | ||
const { tx, agent } = t.context | ||
tx._llmContextManager.run(null, () => { | ||
const llmContext = extractLlmContext(agent) | ||
t.equal(typeof llmContext, 'object') | ||
t.equal(Object.entries(llmContext).length, 0) | ||
t.end() | ||
}) | ||
}) | ||
|
||
t.test('extract LLM context', (t) => { | ||
const { tx, agent } = t.context | ||
tx._llmContextManager.run({ 'llm.test': 1, 'skip': 2 }, () => { | ||
const llmContext = extractLlmContext(agent) | ||
t.equal(llmContext['llm.test'], 1) | ||
t.notOk(llmContext.skip) | ||
t.end() | ||
}) | ||
}) | ||
|
||
t.test('no transaction', (t) => { | ||
const { tx, agent } = t.context | ||
agent.tracer.getTransaction = () => { | ||
return null | ||
} | ||
tx._llmContextManager.run(null, () => { | ||
const llmContext = extractLlmContext(agent) | ||
t.equal(typeof llmContext, 'object') | ||
t.equal(Object.entries(llmContext).length, 0) | ||
t.end() | ||
}) | ||
}) | ||
t.end() | ||
}) |
Oops, something went wrong.