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

feat: Added new API method withLlmCustomAttributes to run a function in a LLM context #2437

Merged
merged 39 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
2361240
feat: Set LLM events custom attributes
MikeVaz Jul 16, 2024
4dd3be8
fix: Debug statements
MikeVaz Jul 16, 2024
55da778
fix: Example
MikeVaz Jul 16, 2024
720be07
fix: Add guards
MikeVaz Jul 16, 2024
878d6b5
fix: Add unit test
MikeVaz Jul 17, 2024
de8b498
feat: WithLlmCustomAttributes
MikeVaz Jul 22, 2024
dc0261a
feat: Merge parent context into children
RyanKadri Jul 23, 2024
07cbcab
Merge branch 'newrelic:main' into setLlmCustomAttributes
MikeVaz Jul 29, 2024
08927ec
feat: Option 4
MikeVaz Jul 31, 2024
8b0cd84
fix: Unnecessary test
MikeVaz Jul 31, 2024
7ab7bcd
fix: Test name
MikeVaz Jul 31, 2024
0b2f2d6
fix: Integration tests
MikeVaz Aug 1, 2024
f75dabf
fix: Remove extra npm scripts
MikeVaz Aug 2, 2024
19a9e80
fix: Remove extra npm scripts
MikeVaz Aug 2, 2024
24a13bd
fix: PR feedback
MikeVaz Aug 2, 2024
af4aafa
fix: PR feedback
MikeVaz Aug 2, 2024
d428aaa
fix: PR feedback
MikeVaz Aug 5, 2024
d2264ad
fix: Unit test and pr feedback
MikeVaz Aug 5, 2024
807f0cf
Merge branch 'newrelic:main' into setLlmCustomAttributes
MikeVaz Aug 13, 2024
ef488da
fix: PR feedback
MikeVaz Aug 13, 2024
bcc9e15
fix: Typo
MikeVaz Aug 13, 2024
cef4289
fix: PR feedback
MikeVaz Aug 13, 2024
87588e3
fix: PR feedback
MikeVaz Aug 13, 2024
d0101f6
fix: Unit test
MikeVaz Aug 13, 2024
dbd7282
Merge branch 'newrelic:main' into setLlmCustomAttributes
MikeVaz Aug 14, 2024
e7531d7
fix: Apply solution 1
MikeVaz Aug 14, 2024
308bdeb
Update lib/util/llm-utils.js
MikeVaz Aug 15, 2024
a32f932
fix: PR feedback
MikeVaz Aug 15, 2024
ba516b0
Merge branch 'newrelic:main' into setLlmCustomAttributes
MikeVaz Aug 16, 2024
6e629f9
fix: PR feedback
MikeVaz Aug 20, 2024
a8cbb8a
Merge branch 'newrelic:main' into setLlmCustomAttributes
MikeVaz Aug 20, 2024
6d3bd2d
fix: PR feedback
MikeVaz Aug 20, 2024
1f2d240
Update lib/instrumentation/openai.js
MikeVaz Aug 21, 2024
8cc326f
Update lib/instrumentation/langchain/common.js
MikeVaz Aug 21, 2024
cab2f74
Update lib/instrumentation/aws-sdk/v3/bedrock.js
MikeVaz Aug 21, 2024
67a35de
fix: Improve test coverage
MikeVaz Aug 21, 2024
d8353ff
Merge branch 'setLlmCustomAttributes' of https://github.com/MikeVaz/n…
MikeVaz Aug 21, 2024
cb423d6
fix: More unit test and PR feedback
MikeVaz Aug 21, 2024
21e4b5f
chore: Addressed code review feedback
bizob2828 Aug 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api.js
Original file line number Diff line number Diff line change
Expand Up @@ -1932,7 +1932,7 @@ API.prototype.withLlmCustomAttributes = function withLlmCustomAttributes(context
return callback()
}

for (const [key, value] of Object.entries(context)) {
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]
Expand Down
10 changes: 5 additions & 5 deletions lib/util/llm-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@

'use strict'

exports = module.exports = { extractLlmContext, extractLlmAttribtues }
exports = module.exports = { extractLlmContext, extractLlmAttributes }

/**
* Extract LLM attributes from the LLM context
*
* @param {Object} context LLM context object
* @returns {Object} LLM custom attributes
*/
function extractLlmAttribtues(context) {
return Object.keys(context || {}).reduce((result, key) => {
function extractLlmAttributes(context) {
return Object.keys(context).reduce((result, key) => {
if (key.indexOf('llm.') === 0) {
result[key] = context[key]
}
Expand All @@ -29,6 +29,6 @@ function extractLlmAttribtues(context) {
* @returns {Object} LLM context object
*/
function extractLlmContext(agent) {
const context = agent.tracer.getTransaction()._llmContextManager?.getStore() || {}
return extractLlmAttribtues(context)
const context = agent.tracer.getTransaction()?._llmContextManager?.getStore() || {}
return extractLlmAttributes(context)
}
88 changes: 70 additions & 18 deletions test/unit/api/api-llm.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,27 +121,58 @@ tap.test('Agent API LLM methods', (t) => {
})
})

t.test('withLlmCustomAttributes', (t) => {
t.test('withLlmCustomAttributes should handle no active transaction', (t) => {
bizob2828 marked this conversation as resolved.
Show resolved Hide resolved
const { api } = t.context
t.doesNotThrow(() => {
t.autoend()

t.equal(
api.withLlmCustomAttributes({ test: 1 }, () => {
t.equal(loggerMock.warn.callCount, 1)
return 1
}),
1
)
})

t.test('withLlmCustomAttributes should handle an empty store', (t) => {
const { api } = t.context
const agent = api.agent

t.autoend()
helper.runInTransaction(api.agent, (tx) => {
agent.tracer.getTransaction = () => {
return tx
}
t.equal(
api.withLlmCustomAttributes({ test: 1 }, () => {
t.equal(loggerMock.warn.callCount, 1)
api.withLlmCustomAttributes(null, () => {
return 1
}),
1
)
})
})

t.test('withLlmCustomAttributes should handle no callback', (t) => {
const { api } = t.context
const agent = api.agent
t.autoend()
helper.runInTransaction(api.agent, (tx) => {
t.context.agent.tracer.getTransaction = () => {
agent.tracer.getTransaction = () => {
return tx
}
api.withLlmCustomAttributes({ test: 1 }, null)
t.equal(loggerMock.warn.callCount, 1)
})
})

t.doesNotThrow(() => {
api.withLlmCustomAttributes(null, null)
t.equal(loggerMock.warn.callCount, 2)
})

t.test('withLlmCustomAttributes should normalize attributes', (t) => {
const { api } = t.context
const agent = api.agent
t.autoend()
helper.runInTransaction(api.agent, (tx) => {
agent.tracer.getTransaction = () => {
return tx
}
api.withLlmCustomAttributes(
{
'toRename': 'value1',
Expand All @@ -160,19 +191,40 @@ tap.test('Agent API LLM methods', (t) => {
t.notOk(parentContext.toDelete3)
t.equal(parentContext['llm.number'], 1)
t.equal(parentContext['llm.boolean'], true)

api.withLlmCustomAttributes({ 'llm.someAttribute': 'someValue' }, () => {
const contextManager = tx._llmContextManager
const context = contextManager.getStore()
t.equal(context[`llm.toRename`], 'value1')
t.equal(context['llm.someAttribute'], 'someValue')
t.end()
})
}
)
})
})

t.test('withLlmCustomAttributes should support branching', (t) => {
const { api } = t.context
const agent = api.agent
t.autoend()
helper.runInTransaction(api.agent, (tx) => {
agent.tracer.getTransaction = () => {
return tx
}
api.withLlmCustomAttributes({ 'llm.step': '1', 'llm.path': 'root' }, () => {
const contextManager = tx._llmContextManager
const context = contextManager.getStore()
t.equal(context[`llm.step`], '1')
t.equal(context['llm.path'], 'root')
api.withLlmCustomAttributes({ 'llm.step': '1.1', 'llm.path': 'root/1' }, () => {
const contextManager = tx._llmContextManager
const context = contextManager.getStore()
t.equal(context[`llm.step`], '1.1')
t.equal(context['llm.path'], 'root/1')
})
api.withLlmCustomAttributes({ 'llm.step': '1.2', 'llm.path': 'root/2' }, () => {
const contextManager = tx._llmContextManager
const context = contextManager.getStore()
t.equal(context[`llm.step`], '1.2')
t.equal(context['llm.path'], 'root/2')
})
})
})
})

t.test('setLlmTokenCount should register callback to calculate token counts', async (t) => {
const { api, agent } = t.context
function callback(model, content) {
Expand Down
60 changes: 41 additions & 19 deletions test/unit/util/llm-utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
'use strict'

const tap = require('tap')
const { extractLlmAttribtues, extractLlmContext } = require('../../../lib/util/llm-utils')
const { extractLlmAttributes, extractLlmContext } = require('../../../lib/util/llm-utils')
const { AsyncLocalStorage } = require('async_hooks')

tap.test('extractLlmAttributes', (t) => {
Expand All @@ -16,35 +16,57 @@ tap.test('extractLlmAttributes', (t) => {
'fllm.skip': 3
}

const llmContext = extractLlmAttribtues(context)
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) => {
bizob2828 marked this conversation as resolved.
Show resolved Hide resolved
const tx = {
_llmContextManager: new AsyncLocalStorage()
}
const agent = {
tracer: {
getTransaction: () => {
return tx
let tx
let agent
t.autoend()
t.beforeEach(() => {
tx = {
_llmContextManager: new AsyncLocalStorage()
}
agent = {
tracer: {
getTransaction: () => {
return tx
}
}
}
}
})

tx._llmContextManager.run(null, () => {
const llmContext = extractLlmContext(agent)
t.equal(typeof llmContext, 'object')
t.equal(Object.entries(llmContext).length, 0)
t.test('handle empty context', (t) => {
t.autoend()
tx._llmContextManager.run(null, () => {
const llmContext = extractLlmContext(agent)
t.equal(typeof llmContext, 'object')
t.equal(Object.entries(llmContext).length, 0)
})
})

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('extract LLM context', (t) => {
t.autoend()
tx._llmContextManager.run({ 'llm.test': 1, 'skip': 2 }, () => {
const llmContext = extractLlmContext(agent)
t.equal(llmContext['llm.test'], 1)
t.notOk(llmContext.skip)
})
})

t.test('no transaction', (t) => {
t.autoend()
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)
})
})
})