@@ -65,4 +65,215 @@ describe("decorators", () => {
6565 throw error ;
6666 }
6767 } ) ;
68+
69+ it ( "should create logs with proper tracing when using prompt in flow decorator" , async ( ) => {
70+ // Track resources for cleanup
71+ let flowId : string | null = null ;
72+ let promptId : string | null = null ;
73+
74+ try {
75+ // Create test flow and prompt paths
76+ const flowPath = `${ testSetup . sdkTestDir . path } /test_flow` ;
77+ const promptPath = `${ testSetup . sdkTestDir . path } /test_prompt` ;
78+
79+ // Create the prompt
80+ const promptResponse = await testSetup . humanloopClient . prompts . upsert ( {
81+ path : promptPath ,
82+ provider : "openai" ,
83+ model : "gpt-4o-mini" ,
84+ temperature : 0 ,
85+ } ) ;
86+ promptId = promptResponse . id ;
87+
88+ // Define the flow callable function with the correct type signature
89+ const flowCallable = async ( question : {
90+ question : string ;
91+ } ) : Promise < string > => {
92+ const response = await testSetup . humanloopClient . prompts . call ( {
93+ path : promptPath ,
94+ messages : [ { role : "user" , content : question . question } ] ,
95+ providerApiKeys : { openai : testSetup . openaiApiKey } ,
96+ } ) ;
97+
98+ const output = response . logs ?. [ 0 ] ?. output ;
99+ expect ( output ) . not . toBeNull ( ) ;
100+ return output || "" ;
101+ } ;
102+
103+ // Apply the flow decorator
104+ const myFlow = testSetup . humanloopClient . flow ( {
105+ path : flowPath ,
106+ callable : flowCallable ,
107+ } ) ;
108+
109+ // Call the flow with the expected input format
110+ const result = await myFlow ( {
111+ question : "What is the capital of the France?" ,
112+ } ) ;
113+ expect ( result ?. toLowerCase ( ) ) . toContain ( "paris" ) ;
114+
115+ // Wait for logs to be created
116+ await new Promise ( ( resolve ) => setTimeout ( resolve , 5000 ) ) ;
117+
118+ // Verify prompt logs
119+ const promptRetrieveResponse =
120+ await testSetup . humanloopClient . files . retrieveByPath ( {
121+ path : promptPath ,
122+ } ) ;
123+ expect ( promptRetrieveResponse ) . not . toBeNull ( ) ;
124+ const promptLogsResponse = await testSetup . humanloopClient . logs . list ( {
125+ fileId : promptRetrieveResponse . id ,
126+ page : 1 ,
127+ size : 50 ,
128+ } ) ;
129+ expect ( promptLogsResponse . data . length ) . toBe ( 1 ) ;
130+ const promptLog = promptLogsResponse . data [ 0 ] ;
131+
132+ // Verify flow logs
133+ const flowRetrieveResponse =
134+ await testSetup . humanloopClient . files . retrieveByPath ( {
135+ path : flowPath ,
136+ } ) ;
137+ expect ( flowRetrieveResponse ) . not . toBeNull ( ) ;
138+ flowId = flowRetrieveResponse . id ;
139+ const flowLogsResponse = await testSetup . humanloopClient . logs . list ( {
140+ fileId : flowRetrieveResponse . id ,
141+ page : 1 ,
142+ size : 50 ,
143+ } ) ;
144+ expect ( flowLogsResponse . data . length ) . toBe ( 1 ) ;
145+ const flowLog = flowLogsResponse . data [ 0 ] ;
146+
147+ // Verify tracing between logs
148+ expect ( promptLog . traceParentId ) . toBe ( flowLog . id ) ;
149+ } finally {
150+ // Clean up resources
151+ if ( flowId ) {
152+ await testSetup . humanloopClient . flows . delete ( flowId ) ;
153+ }
154+ if ( promptId ) {
155+ await testSetup . humanloopClient . prompts . delete ( promptId ) ;
156+ }
157+ }
158+ } ) ;
159+
160+ it ( "should log exceptions when using the flow decorator" , async ( ) => {
161+ // Track resources for cleanup
162+ let flowId : string | null = null ;
163+
164+ try {
165+ // Create test flow path
166+ const flowPath = `${ testSetup . sdkTestDir . path } /test_flow_log_error` ;
167+
168+ // Define a flow callable that throws an error
169+ const flowCallable = async ( {
170+ question,
171+ } : {
172+ question : string ;
173+ } ) : Promise < string > => {
174+ throw new Error ( "This is a test exception" ) ;
175+ } ;
176+
177+ // Apply the flow decorator
178+ const myFlow = testSetup . humanloopClient . flow ( {
179+ path : flowPath ,
180+ callable : flowCallable ,
181+ } ) ;
182+
183+ // Call the flow and expect it to throw
184+ try {
185+ await myFlow ( { question : "test" } ) ;
186+ // If we get here, the test should fail
187+ fail ( "Expected flow to throw an error but it didn't" ) ;
188+ } catch ( error ) {
189+ // Expected error
190+ expect ( error ) . toBeDefined ( ) ;
191+ }
192+
193+ // Wait for logs to be created
194+ await new Promise ( ( resolve ) => setTimeout ( resolve , 5000 ) ) ;
195+
196+ // Verify flow logs
197+ const flowRetrieveResponse =
198+ await testSetup . humanloopClient . files . retrieveByPath ( {
199+ path : flowPath ,
200+ } ) ;
201+ expect ( flowRetrieveResponse ) . not . toBeNull ( ) ;
202+ flowId = flowRetrieveResponse . id ;
203+
204+ const flowLogsResponse = await testSetup . humanloopClient . logs . list ( {
205+ fileId : flowRetrieveResponse . id ,
206+ page : 1 ,
207+ size : 50 ,
208+ } ) ;
209+ expect ( flowLogsResponse . data . length ) . toBe ( 1 ) ;
210+
211+ const flowLog = flowLogsResponse . data [ 0 ] ;
212+ expect ( flowLog . error ) . not . toBeUndefined ( ) ;
213+ expect ( flowLog . output ) . toBeUndefined ( ) ;
214+ } finally {
215+ // Clean up resources
216+ if ( flowId ) {
217+ await testSetup . humanloopClient . flows . delete ( flowId ) ;
218+ }
219+ }
220+ } ) ;
221+
222+ it ( "should populate outputMessage when flow returns chat message format" , async ( ) => {
223+ // Track resources for cleanup
224+ let flowId : string | null = null ;
225+
226+ try {
227+ // Create test flow path
228+ const flowPath = `${ testSetup . sdkTestDir . path } /test_flow_log_output_message` ;
229+
230+ // Define a flow callable that returns a chat message format
231+ const flowCallable = async ( { question } : { question : string } ) => {
232+ return {
233+ role : "user" ,
234+ content : question ,
235+ } ;
236+ } ;
237+
238+ // Apply the flow decorator
239+ const myFlow = testSetup . humanloopClient . flow ( {
240+ path : flowPath ,
241+ callable : flowCallable ,
242+ } ) ;
243+
244+ // Call the flow and check the returned message
245+ const result = await myFlow ( {
246+ question : "What is the capital of the France?" ,
247+ } ) ;
248+ expect ( result ?. content . toLowerCase ( ) ) . toContain ( "france" ) ;
249+
250+ // Wait for logs to be created
251+ await new Promise ( ( resolve ) => setTimeout ( resolve , 5000 ) ) ;
252+
253+ // Verify flow logs
254+ const flowRetrieveResponse =
255+ await testSetup . humanloopClient . files . retrieveByPath ( {
256+ path : flowPath ,
257+ } ) ;
258+ expect ( flowRetrieveResponse ) . not . toBeNull ( ) ;
259+ flowId = flowRetrieveResponse . id ;
260+
261+ const flowLogsResponse = await testSetup . humanloopClient . logs . list ( {
262+ fileId : flowRetrieveResponse . id ,
263+ page : 1 ,
264+ size : 50 ,
265+ } ) ;
266+ expect ( flowLogsResponse . data . length ) . toBe ( 1 ) ;
267+
268+ const flowLog = flowLogsResponse . data [ 0 ] ;
269+ expect ( flowLog . outputMessage ) . not . toBeUndefined ( ) ;
270+ expect ( flowLog . output ) . toBeUndefined ( ) ;
271+ expect ( flowLog . error ) . toBeUndefined ( ) ;
272+ } finally {
273+ // Clean up resources
274+ if ( flowId ) {
275+ await testSetup . humanloopClient . flows . delete ( flowId ) ;
276+ }
277+ }
278+ } ) ;
68279} ) ;
0 commit comments