Skip to content

Commit 9cf0246

Browse files
committed
Don't add outgoing tool call with content == ''
1 parent 4ec0d34 commit 9cf0246

File tree

3 files changed

+21
-13
lines changed

3 files changed

+21
-13
lines changed

lib/subscribers/langchain/runnable-stream.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -115,20 +115,21 @@ class LangchainRunnableStreamSubscriber extends LangchainRunnableSubscriber {
115115
* Concats streamed content from various LangChain/LangGraph result formats.
116116
*
117117
* @param {object} result the stream result chunk
118-
* @param {string} content the concated response as a string
119-
* @returns {string} updated content
118+
* @param {string|object} content the response so far
119+
* @returns {string|object} updated response content. For LangGraph, it will return an object
120+
* (e.g. AIMessage), so we have more info if we need to drop this response if it is incomplete
121+
* (e.g outgoing tool call).
120122
*/
121123
concatResponseContent(result, content) {
122124
if (result?.value?.messages || result?.value?.agent?.messages) {
123125
// LangGraph case:
124126
// The result.value.%messages field contains all messages,
125127
// request and response, and appends new events at the
126-
// end of the array. The last result chunk will contain all messages
127-
// in the stream with the final response at the end.
128+
// end of the array. Therefore the las message is the most
129+
// recent response.
128130
const langgraphMessages = result?.value?.messages ?? result?.value?.agent?.messages
129131
if (langgraphMessages.length > 0) {
130-
const lastMsg = langgraphMessages[langgraphMessages.length - 1]
131-
content = lastMsg?.content
132+
content = langgraphMessages[langgraphMessages.length - 1]
132133
}
133134
} else if (typeof result?.value?.content === 'string') {
134135
// LangChain MessageChunk case

lib/subscribers/langchain/runnable.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,20 @@ class LangchainRunnableSubscriber extends AiMonitoringChatSubscriber {
5555

5656
getMessages({ request, response }) {
5757
const messages = []
58-
// check if request is truthy and an empty string
5958
if (request || request === '') {
6059
messages.push(request)
6160
}
6261

63-
// check if response is truthy and an empty string
6462
if (response || response === '') {
63+
// via langgraph api, response object lives at `messages[0]`
64+
if (response.messages?.length > 0) {
65+
response = response.messages[0]
66+
}
67+
if (response.content === '' && response?.tool_calls?.length > 0) {
68+
// Do not create a LlmChatCompletionMessage for an outgoing
69+
// tool call with no result yet
70+
return [request]
71+
}
6572
messages.push(response)
6673
}
6774

@@ -94,11 +101,6 @@ class LangchainRunnableSubscriber extends AiMonitoringChatSubscriber {
94101
* @returns {object} an object with `content` (string) and `role` (string)
95102
*/
96103
extractContentAndRole(msg) {
97-
if (msg?.messages) {
98-
// Typical structure for LangGraph
99-
msg = msg.messages[0]
100-
}
101-
102104
// Get message content
103105
let content = ''
104106
if (typeof msg === 'string') {

test/versioned/langgraph/graph-stream.test.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,11 @@ test('should have LlmTool events from LangChain instrumentation', async (t) => {
265265
const chatEvents = events.filter((e) => e[0].type === 'LlmChatCompletionMessage' && e[1].role === 'tool')
266266
assert.equal(chatEvents.length, 2, 'should have one tool message for openai and another for langchain')
267267

268+
const langchainEvents = events.filter((e) => e[0].type === 'LlmChatCompletionMessage' && e[1].vendor === 'langchain')
269+
langchainEvents.forEach((msg) => {
270+
assert.ok(msg?.[1]?.content?.length > 0, 'should have non-empty content')
271+
})
272+
268273
tx.end()
269274
})
270275
})

0 commit comments

Comments
 (0)