@@ -19,6 +19,27 @@ const openAiBaseEmbeddingInfo = { base: 'https://api.openai.com', path: '/v1/emb
19
19
20
20
const isDdTrace = iastFilter . isDdTrace
21
21
22
+ function stubSingleEmbedding ( langchainOpenaiOpenAiVersion ) {
23
+ if ( semver . satisfies ( langchainOpenaiOpenAiVersion , '>=4.91.0' ) ) {
24
+ stubCall ( {
25
+ ...openAiBaseEmbeddingInfo ,
26
+ response : require ( './fixtures/single-embedding.json' )
27
+ } )
28
+ } else {
29
+ stubCall ( {
30
+ ...openAiBaseEmbeddingInfo ,
31
+ response : {
32
+ object : 'list' ,
33
+ data : [ {
34
+ object : 'embedding' ,
35
+ index : 0 ,
36
+ embedding : Array ( 1536 ) . fill ( 0 )
37
+ } ]
38
+ }
39
+ } )
40
+ }
41
+ }
42
+
22
43
describe ( 'Plugin' , ( ) => {
23
44
let langchainOpenai
24
45
let langchainAnthropic
@@ -28,7 +49,8 @@ describe('Plugin', () => {
28
49
let langchainOutputParsers
29
50
let langchainPrompts
30
51
let langchainRunnables
31
-
52
+ let langchainTools
53
+ let MemoryVectorStore
32
54
/**
33
55
* In OpenAI 4.91.0, the default response format for embeddings was changed from `float` to `base64`.
34
56
* We do not have control in @langchain/openai embeddings to change this for an individual call,
@@ -65,7 +87,8 @@ describe('Plugin', () => {
65
87
} )
66
88
67
89
beforeEach ( ( ) => {
68
- langchainOpenai = require ( `../../../versions/@langchain/openai@${ version } ` ) . get ( )
90
+ langchainOpenai = require ( `../../../versions/langchain@${ version } ` )
91
+ . get ( '@langchain/openai' )
69
92
langchainAnthropic = require ( `../../../versions/@langchain/anthropic@${ version } ` ) . get ( )
70
93
if ( version !== '0.1.0' ) {
71
94
// version mismatching otherwise
@@ -80,10 +103,17 @@ describe('Plugin', () => {
80
103
langchainPrompts = require ( `../../../versions/@langchain/core@${ version } ` ) . get ( '@langchain/core/prompts' )
81
104
langchainRunnables = require ( `../../../versions/@langchain/core@${ version } ` ) . get ( '@langchain/core/runnables' )
82
105
106
+ langchainTools = require ( `../../../versions/@langchain/core@${ version } ` )
107
+ . get ( '@langchain/core/tools' )
108
+
109
+ MemoryVectorStore = require ( `../../../versions/@langchain/core@${ version } ` )
110
+ . get ( 'langchain/vectorstores/memory' )
111
+ . MemoryVectorStore
112
+
83
113
langchainOpenaiOpenAiVersion =
84
- require ( `../../../versions/@ langchain/openai @${ version } ` )
85
- . get ( 'openai/version' )
86
- . VERSION
114
+ require ( `../../../versions/langchain@${ version } ` )
115
+ . get ( 'openai/version' )
116
+ . VERSION
87
117
} )
88
118
89
119
afterEach ( ( ) => {
@@ -1013,6 +1043,133 @@ describe('Plugin', () => {
1013
1043
} )
1014
1044
} )
1015
1045
} )
1046
+
1047
+ describe ( 'tools' , ( ) => {
1048
+ it ( 'traces a tool call' , async function ( ) {
1049
+ if ( ! langchainTools ?. tool ) this . skip ( )
1050
+
1051
+ const myTool = langchainTools . tool (
1052
+ ( ) => 'Hello, world!' ,
1053
+ {
1054
+ name : 'myTool' ,
1055
+ description : 'A tool that returns a greeting'
1056
+ }
1057
+ )
1058
+
1059
+ const checkTraces = agent . assertSomeTraces ( traces => {
1060
+ const span = traces [ 0 ] [ 0 ]
1061
+
1062
+ expect ( span ) . to . have . property ( 'name' , 'langchain.request' )
1063
+ expect ( span . resource ) . to . match ( / ^ l a n g c h a i n \. t o o l s \. [ ^ . ] + \. m y T o o l $ / )
1064
+ } )
1065
+ const result = await myTool . invoke ( )
1066
+ expect ( result ) . to . equal ( 'Hello, world!' )
1067
+
1068
+ await checkTraces
1069
+ } )
1070
+
1071
+ it ( 'traces a tool call with an error' , async function ( ) {
1072
+ if ( ! langchainTools ?. tool ) this . skip ( )
1073
+
1074
+ const myTool = langchainTools . tool (
1075
+ ( ) => { throw new Error ( 'This is a test error' ) } ,
1076
+ {
1077
+ name : 'myTool' ,
1078
+ description : 'A tool that throws an error'
1079
+ }
1080
+ )
1081
+
1082
+ const checkTraces = agent . assertSomeTraces ( traces => {
1083
+ const span = traces [ 0 ] [ 0 ]
1084
+
1085
+ expect ( span ) . to . have . property ( 'name' , 'langchain.request' )
1086
+ expect ( span . resource ) . to . match ( / ^ l a n g c h a i n \. t o o l s \. [ ^ . ] + \. m y T o o l $ / )
1087
+
1088
+ expect ( span . meta ) . to . have . property ( 'error.message' )
1089
+ expect ( span . meta ) . to . have . property ( 'error.type' )
1090
+ expect ( span . meta ) . to . have . property ( 'error.stack' )
1091
+ } )
1092
+
1093
+ try {
1094
+ await myTool . invoke ( )
1095
+ expect . fail ( 'Expected an error to be thrown' )
1096
+ } catch { }
1097
+
1098
+ await checkTraces
1099
+ } )
1100
+ } )
1101
+
1102
+ describe ( 'vectorstores' , ( ) => {
1103
+ let vectorstore
1104
+
1105
+ beforeEach ( async ( ) => {
1106
+ // need to mock out adding a document to the vectorstore
1107
+ stubSingleEmbedding ( langchainOpenaiOpenAiVersion )
1108
+
1109
+ const embeddings = new langchainOpenai . OpenAIEmbeddings ( )
1110
+ vectorstore = new MemoryVectorStore ( embeddings )
1111
+
1112
+ const document = {
1113
+ pageContent : 'The powerhouse of the cell is the mitochondria' ,
1114
+ metadata : { source : 'https://example.com' } ,
1115
+ id : '1'
1116
+ }
1117
+
1118
+ return vectorstore . addDocuments ( [ document ] )
1119
+ } )
1120
+
1121
+ it ( 'traces a vectorstore similaritySearch call' , async ( ) => {
1122
+ stubSingleEmbedding ( langchainOpenaiOpenAiVersion )
1123
+
1124
+ const checkTraces = agent . assertSomeTraces ( traces => {
1125
+ const spans = traces [ 0 ]
1126
+
1127
+ expect ( spans ) . to . have . length ( 2 )
1128
+
1129
+ const vectorstoreSpan = spans [ 0 ]
1130
+ const embeddingSpan = spans [ 1 ]
1131
+
1132
+ expect ( vectorstoreSpan ) . to . have . property ( 'name' , 'langchain.request' )
1133
+ expect ( vectorstoreSpan ) . to . have . property ( 'resource' , 'langchain.vectorstores.memory.MemoryVectorStore' )
1134
+
1135
+ expect ( embeddingSpan ) . to . have . property ( 'name' , 'langchain.request' )
1136
+ expect ( embeddingSpan ) . to . have . property ( 'resource' , 'langchain.embeddings.openai.OpenAIEmbeddings' )
1137
+ } , { spanResourceMatch : / l a n g c h a i n \. v e c t o r s t o r e s \. m e m o r y \. M e m o r y V e c t o r S t o r e / } )
1138
+ // we need the spanResourceMatch, otherwise we'll match from the beforeEach
1139
+
1140
+ const result = await vectorstore . similaritySearch ( 'The powerhouse of the cell is the mitochondria' , 2 )
1141
+ expect ( result ) . to . exist
1142
+
1143
+ await checkTraces
1144
+ } )
1145
+
1146
+ it ( 'traces a vectorstore similaritySearchWithScore call' , async ( ) => {
1147
+ stubSingleEmbedding ( langchainOpenaiOpenAiVersion )
1148
+
1149
+ const checkTraces = agent . assertSomeTraces ( traces => {
1150
+ const spans = traces [ 0 ]
1151
+
1152
+ expect ( spans ) . to . have . length ( 2 )
1153
+
1154
+ const vectorstoreSpan = spans [ 0 ]
1155
+ const embeddingSpan = spans [ 1 ]
1156
+
1157
+ expect ( vectorstoreSpan ) . to . have . property ( 'name' , 'langchain.request' )
1158
+ expect ( vectorstoreSpan ) . to . have . property ( 'resource' , 'langchain.vectorstores.memory.MemoryVectorStore' )
1159
+
1160
+ expect ( embeddingSpan ) . to . have . property ( 'name' , 'langchain.request' )
1161
+ expect ( embeddingSpan ) . to . have . property ( 'resource' , 'langchain.embeddings.openai.OpenAIEmbeddings' )
1162
+ } , { spanResourceMatch : / l a n g c h a i n \. v e c t o r s t o r e s \. m e m o r y \. M e m o r y V e c t o r S t o r e / } )
1163
+ // we need the spanResourceMatch, otherwise we'll match from the beforeEach
1164
+
1165
+ const result = await vectorstore . similaritySearchWithScore (
1166
+ 'The powerhouse of the cell is the mitochondria' , 2
1167
+ )
1168
+ expect ( result ) . to . exist
1169
+
1170
+ await checkTraces
1171
+ } )
1172
+ } )
1016
1173
} )
1017
1174
} )
1018
1175
} )
0 commit comments