6
6
'use strict'
7
7
8
8
const common = require ( './common' )
9
- const {
10
- AI : { LANGCHAIN }
11
- } = require ( '../../metrics/names' )
9
+ const { AI } = require ( '../../metrics/names' )
10
+ const { LANGCHAIN } = AI
12
11
const {
13
12
LangChainCompletionMessage,
14
13
LangChainCompletionSummary
@@ -28,10 +27,33 @@ module.exports = function initialize(shim, langchain) {
28
27
return
29
28
}
30
29
30
+ instrumentInvokeChain ( { langchain, shim } )
31
+
32
+ if ( agent . config . ai_monitoring . streaming . enabled ) {
33
+ instrumentStream ( { langchain, shim } )
34
+ } else {
35
+ shim . logger . warn (
36
+ '`ai_monitoring.streaming.enabled` is set to `false`, stream will not be instrumented.'
37
+ )
38
+ agent . metrics . getOrCreateMetric ( AI . STREAMING_DISABLED ) . incrementCallCount ( )
39
+ agent . metrics
40
+ . getOrCreateMetric ( `${ LANGCHAIN . TRACKING_PREFIX } /${ pkgVersion } ` )
41
+ . incrementCallCount ( )
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Instruments and records span and relevant LLM events for `chain.invoke`
47
+ *
48
+ * @param {object } params function params
49
+ * @param {object } params.langchain `@langchain/core/runnables/base` export
50
+ * @param {Shim } params.shim instace of shim
51
+ */
52
+ function instrumentInvokeChain ( { langchain, shim } ) {
31
53
shim . record (
32
54
langchain . RunnableSequence . prototype ,
33
55
'invoke' ,
34
- function wrapCall ( shim , invoke , fnName , args ) {
56
+ function wrapCall ( shim , _invoke , fnName , args ) {
35
57
const [ request , params ] = args
36
58
const metadata = params ?. metadata ?? { }
37
59
const tags = params ?. tags ?? [ ]
@@ -41,53 +63,183 @@ module.exports = function initialize(shim, langchain) {
41
63
promise : true ,
42
64
// eslint-disable-next-line max-params
43
65
after ( _shim , _fn , _name , err , output , segment ) {
44
- segment . end ( )
45
- const completionSummary = new LangChainCompletionSummary ( {
46
- agent,
47
- messages : [ { output } ] ,
48
- metadata,
49
- tags,
66
+ recordChatCompletionEvents ( {
50
67
segment,
51
- error : err != null ,
52
- runId : segment [ langchainRunId ]
53
- } )
54
-
55
- common . recordEvent ( {
56
- agent,
57
- type : 'LlmChatCompletionSummary' ,
58
- pkgVersion,
59
- msg : completionSummary
60
- } )
61
-
62
- // output can be BaseMessage with a content property https://js.langchain.com/docs/modules/model_io/concepts#messages
63
- // or an output parser https://js.langchain.com/docs/modules/model_io/concepts#output-parsers
64
- recordCompletions ( {
68
+ messages : [ output ] ,
65
69
events : [ request , output ] ,
66
- completionSummary ,
67
- agent ,
68
- segment ,
70
+ metadata ,
71
+ tags ,
72
+ err ,
69
73
shim
70
74
} )
75
+ }
76
+ } )
77
+ }
78
+ )
79
+ }
80
+
81
+ /**
82
+ * Instruments and records span and relevant LLM events for `chain.stream`
83
+ *
84
+ * @param {object } params function params
85
+ * @param {object } params.langchain `@langchain/core/runnables/base` export
86
+ * @param {Shim } params.shim instace of shim
87
+ */
88
+ function instrumentStream ( { langchain, shim } ) {
89
+ shim . record (
90
+ langchain . RunnableSequence . prototype ,
91
+ 'stream' ,
92
+ function wrapStream ( shim , _stream , fnName , args ) {
93
+ const [ request , params ] = args
94
+ const metadata = params ?. metadata ?? { }
95
+ const tags = params ?. tags ?? [ ]
71
96
72
- if ( err ) {
73
- agent . errors . add (
74
- segment . transaction ,
97
+ return new RecorderSpec ( {
98
+ name : `${ LANGCHAIN . CHAIN } /${ fnName } ` ,
99
+ promise : true ,
100
+ // eslint-disable-next-line max-params
101
+ after ( _shim , _fn , _name , err , output , segment ) {
102
+ // Input error occurred which means a stream was not created.
103
+ // Skip instrumenting streaming and create Llm Events from
104
+ // the data we have
105
+ if ( output ?. next ) {
106
+ wrapNextHandler ( { shim, output, segment, request, metadata, tags } )
107
+ } else {
108
+ recordChatCompletionEvents ( {
109
+ segment,
110
+ messages : [ ] ,
111
+ events : [ request ] ,
112
+ metadata,
113
+ tags,
75
114
err,
76
- new LlmErrorMessage ( {
77
- response : { } ,
78
- cause : err ,
79
- summary : completionSummary
80
- } )
81
- )
115
+ shim
116
+ } )
82
117
}
83
-
84
- segment . transaction . trace . attributes . addAttribute ( DESTINATIONS . TRANS_EVENT , 'llm' , true )
85
118
}
86
119
} )
87
120
}
88
121
)
89
122
}
90
123
124
+ /**
125
+ * Wraps the next method on the IterableReadableStream. It will also record the Llm
126
+ * events when the stream is done processing.
127
+ *
128
+ * @param {object } params function params
129
+ * @param {Shim } params.shim shim instance
130
+ * @param {TraceSegment } params.segment active segment
131
+ * @param {function } params.output IterableReadableStream
132
+ * @param {string } params.request the prompt message
133
+ * @param {object } params.metadata metadata for the call
134
+ * @param {Array } params.tags tags for the call
135
+ */
136
+ function wrapNextHandler ( { shim, output, segment, request, metadata, tags } ) {
137
+ shim . wrap ( output , 'next' , function wrapIterator ( shim , orig ) {
138
+ let content = ''
139
+ return async function wrappedIterator ( ) {
140
+ try {
141
+ const result = await orig . apply ( this , arguments )
142
+ // only create Llm events when stream iteration is done
143
+ if ( result ?. done ) {
144
+ recordChatCompletionEvents ( {
145
+ segment,
146
+ messages : [ content ] ,
147
+ events : [ request , content ] ,
148
+ metadata,
149
+ tags,
150
+ shim
151
+ } )
152
+ } else {
153
+ content += result . value
154
+ }
155
+ return result
156
+ } catch ( error ) {
157
+ recordChatCompletionEvents ( {
158
+ segment,
159
+ messages : [ content ] ,
160
+ events : [ request , content ] ,
161
+ metadata,
162
+ tags,
163
+ err : error ,
164
+ shim
165
+ } )
166
+ throw error
167
+ } finally {
168
+ // update segment duration on every stream iteration to extend
169
+ // the timer
170
+ segment . touch ( )
171
+ }
172
+ }
173
+ } )
174
+ }
175
+
176
+ /**
177
+ * Ends active segment, creates LlmChatCompletionSummary, and LlmChatCompletionMessage(s), and handles errors if they exists
178
+ *
179
+ * @param {object } params function params
180
+ * @param {TraceSegment } params.segment active segment
181
+ * @param {Array } params.messages response messages
182
+ * @param {Array } params.events prompt and response messages
183
+ * @param {object } params.metadata metadata for the call
184
+ * @param {Array } params.tags tags for the call
185
+ * @param {Error } params.err error object from call
186
+ * @param {Shim } params.shim shim instance
187
+ */
188
+ function recordChatCompletionEvents ( { segment, messages, events, metadata, tags, err, shim } ) {
189
+ const { pkgVersion, agent } = shim
190
+ segment . end ( )
191
+ const completionSummary = new LangChainCompletionSummary ( {
192
+ agent,
193
+ messages,
194
+ metadata,
195
+ tags,
196
+ segment,
197
+ error : err != null ,
198
+ runId : segment [ langchainRunId ]
199
+ } )
200
+
201
+ common . recordEvent ( {
202
+ agent,
203
+ type : 'LlmChatCompletionSummary' ,
204
+ pkgVersion,
205
+ msg : completionSummary
206
+ } )
207
+
208
+ // output can be BaseMessage with a content property https://js.langchain.com/docs/modules/model_io/concepts#messages
209
+ // or an output parser https://js.langchain.com/docs/modules/model_io/concepts#output-parsers
210
+ recordCompletions ( {
211
+ events,
212
+ completionSummary,
213
+ agent,
214
+ segment,
215
+ shim
216
+ } )
217
+
218
+ if ( err ) {
219
+ agent . errors . add (
220
+ segment . transaction ,
221
+ err ,
222
+ new LlmErrorMessage ( {
223
+ response : { } ,
224
+ cause : err ,
225
+ summary : completionSummary
226
+ } )
227
+ )
228
+ }
229
+
230
+ segment . transaction . trace . attributes . addAttribute ( DESTINATIONS . TRANS_EVENT , 'llm' , true )
231
+ }
232
+
233
+ /**
234
+ * Records the LlmChatCompletionMessage(s)
235
+ *
236
+ * @param {object } params function params
237
+ * @param {Array } params.events prompt and response messages
238
+ * @param {LangChainCompletionSummary } params.completionSummary LlmChatCompletionSummary event
239
+ * @param {Agent } params.agent instance of agent
240
+ * @param {TraceSegment } params.segment active segment
241
+ * @param {Shim } params.shim shim instance
242
+ */
91
243
function recordCompletions ( { events, completionSummary, agent, segment, shim } ) {
92
244
for ( let i = 0 ; i < events . length ; i += 1 ) {
93
245
let msg = events [ i ]
0 commit comments