langwatch 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,122 +1,42 @@
1
1
  import 'server-only'
2
2
 
3
+ import { openai } from '@ai-sdk/openai'
3
4
  import {
4
5
  createAI,
5
6
  createStreamableUI,
6
- getMutableAIState,
7
7
  getAIState,
8
- streamUI,
9
- createStreamableValue
8
+ getMutableAIState
10
9
  } from 'ai/rsc'
11
- import { openai } from '@ai-sdk/openai'
12
10
 
13
11
  import {
14
- spinner,
15
12
  BotCard,
16
13
  BotMessage,
17
- SystemMessage,
14
+ Purchase,
15
+ spinner,
18
16
  Stock,
19
- Purchase
17
+ SystemMessage
20
18
  } from '@/components/stocks'
21
19
 
22
- import { z } from 'zod'
23
- import { EventsSkeleton } from '@/components/stocks/events-skeleton'
20
+ import { saveChat } from '@/app/actions'
21
+ import { auth } from '@/auth'
24
22
  import { Events } from '@/components/stocks/events'
25
- import { StocksSkeleton } from '@/components/stocks/stocks-skeleton'
23
+ import { UserMessage } from '@/components/stocks/message'
26
24
  import { Stocks } from '@/components/stocks/stocks'
27
- import { StockSkeleton } from '@/components/stocks/stock-skeleton'
25
+ import { Chat, Message } from '@/lib/types'
28
26
  import {
29
27
  formatNumber,
28
+ nanoid,
30
29
  runAsyncFnWithoutBlocking,
31
- sleep,
32
- nanoid
30
+ sleep
33
31
  } from '@/lib/utils'
34
- import { saveChat } from '@/app/actions'
35
- import { SpinnerMessage, UserMessage } from '@/components/stocks/message'
36
- import { Chat, Message } from '@/lib/types'
37
- import { auth } from '@/auth'
38
- import { LangWatch, convertFromVercelAIMessages } from 'langwatch'
39
-
40
- async function confirmPurchase(symbol: string, price: number, amount: number) {
41
- 'use server'
42
-
43
- const aiState = getMutableAIState<typeof AI>()
44
-
45
- const purchasing = createStreamableUI(
46
- <div className="inline-flex items-start gap-1 md:items-center">
47
- {spinner}
48
- <p className="mb-2">
49
- Purchasing {amount} ${symbol}...
50
- </p>
51
- </div>
52
- )
53
-
54
- const systemMessage = createStreamableUI(null)
55
-
56
- runAsyncFnWithoutBlocking(async () => {
57
- await sleep(1000)
58
-
59
- purchasing.update(
60
- <div className="inline-flex items-start gap-1 md:items-center">
61
- {spinner}
62
- <p className="mb-2">
63
- Purchasing {amount} ${symbol}... working on it...
64
- </p>
65
- </div>
66
- )
67
-
68
- await sleep(1000)
69
-
70
- purchasing.done(
71
- <div>
72
- <p className="mb-2">
73
- You have successfully purchased {amount} ${symbol}. Total cost:{' '}
74
- {formatNumber(amount * price)}
75
- </p>
76
- </div>
77
- )
78
-
79
- systemMessage.done(
80
- <SystemMessage>
81
- You have purchased {amount} shares of {symbol} at ${price}. Total cost ={' '}
82
- {formatNumber(amount * price)}.
83
- </SystemMessage>
84
- )
85
-
86
- aiState.done({
87
- ...aiState.get(),
88
- messages: [
89
- ...aiState.get().messages,
90
- {
91
- id: nanoid(),
92
- role: 'system',
93
- content: `[User has purchased ${amount} shares of ${symbol} at ${price}. Total cost = ${
94
- amount * price
95
- }]`
96
- }
97
- ]
98
- })
99
- })
100
-
101
- return {
102
- purchasingUI: purchasing.value,
103
- newMessage: {
104
- id: nanoid(),
105
- display: systemMessage.value
106
- }
107
- }
108
- }
32
+ import { generateText, streamText, tool } from 'ai'
33
+ import { z } from 'zod'
34
+ import { StockSkeleton } from '../../components/stocks/stock-skeleton'
35
+ import { EventsSkeleton } from '../../components/stocks/events-skeleton'
109
36
 
110
37
  async function submitUserMessage(content: string) {
111
38
  'use server'
112
39
 
113
- const langwatch = new LangWatch()
114
- langwatch.on('error', e => {
115
- console.log('Error from LangWatch:', e)
116
- })
117
-
118
- const trace = langwatch.getTrace()
119
-
120
40
  const aiState = getMutableAIState<typeof AI>()
121
41
 
122
42
  aiState.update({
@@ -135,84 +55,42 @@ async function submitUserMessage(content: string) {
135
55
  You are a stock trading conversation bot and you can help users buy stocks, step by step.
136
56
  You and the user can discuss stock prices and the user can adjust the amount of stocks they want to buy, or place an order, in the UI.
137
57
 
138
- Messages inside [] means that it's a UI element or a user event. For example:
139
- - "[Price of AAPL = 100]" means that an interface of the stock price of AAPL is shown to the user.
140
- - "[User has changed the amount of AAPL to 10]" means that the user has changed the amount of AAPL to 10 in the UI.
58
+ To use tools, use the following format:
59
+ - For stock price: show_stock_price(SYMBOL, PRICE, DELTA)
60
+ - For listing stocks: list_stocks([{"symbol": "AAPL", "price": 150.5, "delta": 2.3}, ...])
61
+ - For purchase UI: show_stock_purchase(SYMBOL, PRICE, NUMBER_OF_SHARES)
62
+ - For events: get_events([{"date": "2024-01-01", "headline": "...", "description": "..."}, ...])
141
63
 
142
- If the user requests purchasing a stock, call \`show_stock_purchase_ui\` to show the purchase UI.
143
- If the user just wants the price, call \`show_stock_price\` to show the price.
144
- If you want to show trending stocks, call \`list_stocks\`.
145
- If you want to show events, call \`get_events\`.
146
- If the user wants to sell stock, or complete another impossible task, respond that you are a demo and cannot do that.
64
+ Messages inside [] means that it's a UI element or a user event.`
147
65
 
148
- Besides that, you can also chat with users and do some calculations if needed.`
149
-
150
- const span = trace.startLLMSpan({
151
- model: 'gpt-4o-mini',
152
- input: {
153
- type: 'chat_messages',
154
- value: [
155
- {
156
- role: 'system',
157
- content: system
158
- },
159
- ...convertFromVercelAIMessages(aiState.get().messages)
160
- ]
161
- }
162
- })
66
+ const ui = createStreamableUI(<BotMessage content="" />)
67
+ // let textNode = <BotMessage content={textStream.value} />
68
+ let fullContent = ''
163
69
 
164
70
  const onFinish = (output: Message[]) => {
165
71
  aiState.done({
166
72
  ...aiState.get(),
167
73
  messages: [...aiState.get().messages, ...output]
168
74
  })
169
-
170
- span.end({
171
- output: {
172
- type: 'chat_messages',
173
- value: convertFromVercelAIMessages(output)
174
- }
175
- })
176
75
  }
177
76
 
178
- let textStream: undefined | ReturnType<typeof createStreamableValue<string>>
179
- let textNode: undefined | React.ReactNode
180
-
181
- const result = await streamUI({
182
- model: openai('gpt-3.5-turbo'),
183
- initial: <SpinnerMessage />,
184
- system,
77
+ const stream = streamText({
78
+ model: openai('gpt-4o-mini'),
185
79
  messages: [
186
- ...aiState.get().messages.map((message: any) => ({
187
- role: message.role,
188
- content: message.content,
189
- name: message.name
190
- }))
80
+ {
81
+ role: 'system',
82
+ content: system
83
+ },
84
+ ...aiState.get().messages
191
85
  ],
192
- text: ({ content, done, delta }) => {
193
- if (!textStream) {
194
- textStream = createStreamableValue('')
195
- textNode = <BotMessage content={textStream.value} />
196
- }
197
-
198
- if (done) {
199
- textStream.done()
200
-
201
- onFinish([
202
- {
203
- id: nanoid(),
204
- role: 'assistant',
205
- content
206
- }
207
- ])
208
- } else {
209
- textStream.update(delta)
86
+ experimental_telemetry: {
87
+ isEnabled: true,
88
+ metadata: {
89
+ threadId: aiState.get().chatId
210
90
  }
211
-
212
- return textNode
213
91
  },
214
92
  tools: {
215
- listStocks: {
93
+ listStocks: tool({
216
94
  description: 'List three imaginary stocks that are trending.',
217
95
  parameters: z.object({
218
96
  stocks: z.array(
@@ -223,10 +101,10 @@ async function submitUserMessage(content: string) {
223
101
  })
224
102
  )
225
103
  }),
226
- generate: async function* ({ stocks }) {
227
- yield (
104
+ execute: async ({ stocks }) => {
105
+ ui.update(
228
106
  <BotCard>
229
- <StocksSkeleton />
107
+ <StockSkeleton />
230
108
  </BotCard>
231
109
  )
232
110
 
@@ -261,14 +139,14 @@ async function submitUserMessage(content: string) {
261
139
  }
262
140
  ])
263
141
 
264
- return (
142
+ ui.update(
265
143
  <BotCard>
266
144
  <Stocks props={stocks} />
267
145
  </BotCard>
268
146
  )
269
147
  }
270
- },
271
- showStockPrice: {
148
+ }),
149
+ showStockPrice: tool({
272
150
  description:
273
151
  'Get the current stock price of a given stock or currency. Use this to show the price to the user.',
274
152
  parameters: z.object({
@@ -280,8 +158,8 @@ async function submitUserMessage(content: string) {
280
158
  price: z.number().describe('The price of the stock.'),
281
159
  delta: z.number().describe('The change in price of the stock')
282
160
  }),
283
- generate: async function* ({ symbol, price, delta }) {
284
- yield (
161
+ execute: async ({ symbol, price, delta }) => {
162
+ ui.update(
285
163
  <BotCard>
286
164
  <StockSkeleton />
287
165
  </BotCard>
@@ -318,14 +196,14 @@ async function submitUserMessage(content: string) {
318
196
  }
319
197
  ])
320
198
 
321
- return (
199
+ ui.update(
322
200
  <BotCard>
323
201
  <Stock props={{ symbol, price, delta }} />
324
202
  </BotCard>
325
203
  )
326
204
  }
327
- },
328
- showStockPurchase: {
205
+ }),
206
+ showStockPurchase: tool({
329
207
  description:
330
208
  'Show price and the UI to purchase a stock or currency. Use this if the user wants to purchase a stock or currency.',
331
209
  parameters: z.object({
@@ -341,7 +219,7 @@ async function submitUserMessage(content: string) {
341
219
  'The **number of shares** for a stock or currency to purchase. Can be optional if the user did not specify it.'
342
220
  )
343
221
  }),
344
- generate: async function* ({ symbol, price, numberOfShares = 100 }) {
222
+ execute: async ({ symbol, price, numberOfShares = 100 }) => {
345
223
  const toolCallId = nanoid()
346
224
 
347
225
  if (numberOfShares <= 0 || numberOfShares > 1000) {
@@ -382,7 +260,7 @@ async function submitUserMessage(content: string) {
382
260
  }
383
261
  ])
384
262
 
385
- return <BotMessage content={'Invalid amount'} />
263
+ ui.update(<BotMessage content={'Invalid amount'} />)
386
264
  } else {
387
265
  onFinish([
388
266
  {
@@ -415,7 +293,7 @@ async function submitUserMessage(content: string) {
415
293
  }
416
294
  ])
417
295
 
418
- return (
296
+ ui.update(
419
297
  <BotCard>
420
298
  <Purchase
421
299
  props={{
@@ -429,8 +307,8 @@ async function submitUserMessage(content: string) {
429
307
  )
430
308
  }
431
309
  }
432
- },
433
- getEvents: {
310
+ }),
311
+ getEvents: tool({
434
312
  description:
435
313
  'List funny imaginary events between user highlighted dates that describe stock activity.',
436
314
  parameters: z.object({
@@ -444,8 +322,8 @@ async function submitUserMessage(content: string) {
444
322
  })
445
323
  )
446
324
  }),
447
- generate: async function* ({ events }) {
448
- yield (
325
+ execute: async ({ events }) => {
326
+ ui.update(
449
327
  <BotCard>
450
328
  <EventsSkeleton />
451
329
  </BotCard>
@@ -482,19 +360,113 @@ async function submitUserMessage(content: string) {
482
360
  }
483
361
  ])
484
362
 
485
- return (
363
+ ui.update(
486
364
  <BotCard>
487
365
  <Events props={events} />
488
366
  </BotCard>
489
367
  )
490
368
  }
491
- }
369
+ })
492
370
  }
493
371
  })
494
372
 
373
+ setTimeout(async () => {
374
+ // First, stream all text chunks
375
+ for await (const chunk of stream.textStream) {
376
+ ui.update(<BotMessage content={fullContent} />)
377
+ fullContent += chunk
378
+ }
379
+
380
+ ui.done()
381
+ const toolCalls = await stream.toolCalls
382
+ if (!toolCalls || toolCalls.length == 0) {
383
+ aiState.done({
384
+ ...aiState.get(),
385
+ messages: [
386
+ ...aiState.get().messages,
387
+ {
388
+ id: nanoid(),
389
+ role: 'assistant',
390
+ content: fullContent
391
+ }
392
+ ]
393
+ })
394
+ }
395
+ }, 0)
396
+
495
397
  return {
496
398
  id: nanoid(),
497
- display: result.value
399
+ display: ui.value
400
+ }
401
+ }
402
+
403
+ async function confirmPurchase(symbol: string, price: number, amount: number) {
404
+ 'use server'
405
+
406
+ const aiState = getMutableAIState<typeof AI>()
407
+
408
+ const purchasing = createStreamableUI(
409
+ <div className="inline-flex items-start gap-1 md:items-center">
410
+ {spinner}
411
+ <p className="mb-2">
412
+ Purchasing {amount} ${symbol}...
413
+ </p>
414
+ </div>
415
+ )
416
+
417
+ const systemMessage = createStreamableUI(null)
418
+
419
+ runAsyncFnWithoutBlocking(async () => {
420
+ await sleep(1000)
421
+
422
+ purchasing.update(
423
+ <div className="inline-flex items-start gap-1 md:items-center">
424
+ {spinner}
425
+ <p className="mb-2">
426
+ Purchasing {amount} ${symbol}... working on it...
427
+ </p>
428
+ </div>
429
+ )
430
+
431
+ await sleep(1000)
432
+
433
+ purchasing.done(
434
+ <div>
435
+ <p className="mb-2">
436
+ You have successfully purchased {amount} ${symbol}. Total cost:{' '}
437
+ {formatNumber(amount * price)}
438
+ </p>
439
+ </div>
440
+ )
441
+
442
+ systemMessage.done(
443
+ <SystemMessage>
444
+ You have purchased {amount} shares of {symbol} at ${price}. Total cost ={' '}
445
+ {formatNumber(amount * price)}.
446
+ </SystemMessage>
447
+ )
448
+
449
+ aiState.done({
450
+ ...aiState.get(),
451
+ messages: [
452
+ ...aiState.get().messages,
453
+ {
454
+ id: nanoid(),
455
+ role: 'system',
456
+ content: `[User has purchased ${amount} shares of ${symbol} at ${price}. Total cost = ${
457
+ amount * price
458
+ }]`
459
+ }
460
+ ]
461
+ })
462
+ })
463
+
464
+ return {
465
+ purchasingUI: purchasing.value,
466
+ newMessage: {
467
+ id: nanoid(),
468
+ display: systemMessage.value
469
+ }
498
470
  }
499
471
  }
500
472
 
@@ -1,5 +1,8 @@
1
1
  /** @type {import('next').NextConfig} */
2
2
  module.exports = {
3
+ experimental: {
4
+ instrumentationHook: true,
5
+ },
3
6
  images: {
4
7
  remotePatterns: [
5
8
  {