Skip to content

Commit 51c26d3

Browse files
feat(ai_guard): add telemetry to the SDK
1 parent f2af4fd commit 51c26d3

File tree

3 files changed

+43
-3
lines changed

3 files changed

+43
-3
lines changed

packages/dd-trace/src/aiguard/sdk.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,14 @@ const {
99
AI_GUARD_ACTION_TAG_KEY,
1010
AI_GUARD_BLOCKED_TAG_KEY,
1111
AI_GUARD_META_STRUCT_KEY,
12-
AI_GUARD_TOOL_NAME_TAG_KEY
12+
AI_GUARD_TOOL_NAME_TAG_KEY,
13+
AI_GUARD_TELEMETRY_REQUESTS,
14+
AI_GUARD_TELEMETRY_TRUNCATED
1315
} = require('./tags')
1416
const log = require('../log')
17+
const telemetryMetrics = require('../telemetry/metrics')
18+
19+
const appsecMetrics = telemetryMetrics.manager.namespace('appsec')
1520

1621
const ALLOW = 'ALLOW'
1722

@@ -70,14 +75,22 @@ class AIGuard extends NoopAIGuard {
7075

7176
#truncate (messages) {
7277
const size = Math.min(messages.length, this.#maxMessagesLength)
78+
if (messages.length > size) {
79+
appsecMetrics.count(AI_GUARD_TELEMETRY_TRUNCATED, { type: 'messages' }).inc(1)
80+
}
7381
const result = messages.slice(-size)
7482

83+
let contentTruncated = false
7584
for (let i = 0; i < size; i++) {
7685
const message = result[i]
7786
if (message.content?.length > this.#maxContentSize) {
87+
contentTruncated = true
7888
result[i] = { ...message, content: message.content.slice(0, this.#maxContentSize) }
7989
}
8090
}
91+
if (contentTruncated) {
92+
appsecMetrics.count(AI_GUARD_TELEMETRY_TRUNCATED, { type: 'content' }).inc(1)
93+
}
8194
return result
8295
}
8396

@@ -140,9 +153,11 @@ class AIGuard extends NoopAIGuard {
140153
payload,
141154
{ url: this.#evaluateUrl, headers: this.#headers, timeout: this.#timeout })
142155
} catch (e) {
143-
throw new AIGuardClientError('Unexpected error calling AI Guard service', { cause: e })
156+
appsecMetrics.count(AI_GUARD_TELEMETRY_REQUESTS, { error: true }).inc(1)
157+
throw new AIGuardClientError(`Unexpected error calling AI Guard service: ${e.message}`, { cause: e })
144158
}
145159
if (response.status !== 200) {
160+
appsecMetrics.count(AI_GUARD_TELEMETRY_REQUESTS, { error: true }).inc(1)
146161
throw new AIGuardClientError(
147162
`AI Guard service call failed, status ${response.status}`,
148163
{ errors: response.body?.errors })
@@ -157,8 +172,10 @@ class AIGuard extends NoopAIGuard {
157172
reason = attr.reason
158173
blockingEnabled = attr.is_blocking_enabled ?? false
159174
} catch (e) {
175+
appsecMetrics.count(AI_GUARD_TELEMETRY_REQUESTS, { error: true }).inc(1)
160176
throw new AIGuardClientError(`AI Guard service returned unexpected response : ${response.body}`, { cause: e })
161177
}
178+
appsecMetrics.count(AI_GUARD_TELEMETRY_REQUESTS, { action, error: false, block: block && blockingEnabled }).inc(1)
162179
span.setTag(AI_GUARD_ACTION_TAG_KEY, action)
163180
span.setTag(AI_GUARD_REASON_TAG_KEY, reason)
164181
if (block && blockingEnabled && action !== ALLOW) {

packages/dd-trace/src/aiguard/tags.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,8 @@ module.exports = {
77
AI_GUARD_ACTION_TAG_KEY: 'ai_guard.action',
88
AI_GUARD_REASON_TAG_KEY: 'ai_guard.reason',
99
AI_GUARD_BLOCKED_TAG_KEY: 'ai_guard.blocked',
10-
AI_GUARD_META_STRUCT_KEY: 'ai_guard'
10+
AI_GUARD_META_STRUCT_KEY: 'ai_guard',
11+
12+
AI_GUARD_TELEMETRY_REQUESTS: 'ai_guard.requests',
13+
AI_GUARD_TELEMETRY_TRUNCATED: 'ai_guard.truncated'
1114
}

packages/dd-trace/test/aiguard/index.spec.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ const sinon = require('sinon')
88
const agent = require('../plugins/agent')
99
const NoopAIGuard = require('../../src/aiguard/noop')
1010
const AIGuard = require('../../src/aiguard/sdk')
11+
const telemetryMetrics = require('../../src/telemetry/metrics')
12+
const appsecNamespace = telemetryMetrics.manager.namespace('appsec')
1113

1214
describe('AIGuard SDK', () => {
1315
const config = {
@@ -29,6 +31,7 @@ describe('AIGuard SDK', () => {
2931
}
3032
let tracer
3133
let aiguard
34+
let count, inc
3235

3336
const toolCall = [
3437
{ role: 'system', content: 'You are a beautiful AI assistant' },
@@ -67,13 +70,20 @@ describe('AIGuard SDK', () => {
6770
originalFetch = global.fetch
6871
global.fetch = sinon.stub()
6972

73+
inc = sinon.spy()
74+
count = sinon.stub(appsecNamespace, 'count').returns({
75+
inc
76+
})
77+
appsecNamespace.metrics.clear()
78+
7079
aiguard = new AIGuard(tracer, config)
7180

7281
return agent.load(null, [])
7382
})
7483

7584
afterEach(() => {
7685
global.fetch = originalFetch
86+
sinon.restore()
7787
agent.close()
7888
})
7989

@@ -115,6 +125,10 @@ describe('AIGuard SDK', () => {
115125
}, { rejectFirst: true })
116126
}
117127

128+
const assertTelemetry = (metric, tags) => {
129+
sinon.assert.calledWith(count, metric, tags)
130+
}
131+
118132
const testSuite = [
119133
{ action: 'ALLOW', reason: 'Go ahead' },
120134
{ action: 'DENY', reason: 'Nope' },
@@ -144,6 +158,7 @@ describe('AIGuard SDK', () => {
144158
expect(evaluation.reason).to.equal(reason)
145159
}
146160

161+
assertTelemetry('ai_guard.requests', { error: false, action, block: blocking })
147162
assertFetch(messages)
148163
await assertAIGuardSpan({
149164
'ai_guard.target': target,
@@ -169,6 +184,7 @@ describe('AIGuard SDK', () => {
169184
err.name === 'AIGuardClientError' && JSON.stringify(err.errors) === JSON.stringify(errors)
170185
)
171186

187+
assertTelemetry('ai_guard.requests', { error: true })
172188
assertFetch(toolCall)
173189
await assertAIGuardSpan({
174190
'ai_guard.target': 'tool',
@@ -184,6 +200,7 @@ describe('AIGuard SDK', () => {
184200
err => err.name === 'AIGuardClientError'
185201
)
186202

203+
assertTelemetry('ai_guard.requests', { error: true })
187204
assertFetch(toolCall)
188205
await assertAIGuardSpan({
189206
'ai_guard.target': 'tool',
@@ -199,6 +216,7 @@ describe('AIGuard SDK', () => {
199216
err => err.name === 'AIGuardClientError'
200217
)
201218

219+
assertTelemetry('ai_guard.requests', { error: true })
202220
assertFetch(toolCall)
203221
await assertAIGuardSpan({
204222
'ai_guard.target': 'tool',
@@ -225,6 +243,7 @@ describe('AIGuard SDK', () => {
225243

226244
await aiguard.evaluate(messages)
227245

246+
assertTelemetry('ai_guard.truncated', { type: 'messages' })
228247
assertFetch(messages)
229248
await assertAIGuardSpan(
230249
{ 'ai_guard.target': 'prompt', 'ai_guard.action': 'ALLOW' },
@@ -242,6 +261,7 @@ describe('AIGuard SDK', () => {
242261

243262
await aiguard.evaluate(messages)
244263

264+
assertTelemetry('ai_guard.truncated', { type: 'content' })
245265
assertFetch(messages)
246266
await assertAIGuardSpan(
247267
{ 'ai_guard.target': 'prompt', 'ai_guard.action': 'ALLOW' },

0 commit comments

Comments
 (0)