-
I'm using the Vercel AI Chatbot template, this is my async function submitUserMessage(text: string, images: ImageFile[] = [], files: File[] = []) {
'use server'
const session = await auth()
// IMPR: you should check plans
if (!session?.user?.id && (images.length > 0 || files.length > 0)) {
throw new Error('You need to be authenticated to upload files')
}
const content = await parseContent(text, images, files)
const aiState = getMutableAIState<typeof AI>()
aiState.update({
...aiState.get(),
messages: [
...aiState.get().messages,
{
id: nanoid(),
role: 'user',
content
}
]
})
let textStream: undefined | ReturnType<typeof createStreamableValue<string>>
let textNode: undefined | React.ReactNode
const result = await streamUI({
model: openai('gpt-3.5-turbo'),
initial: <SpinnerMessage />,
system: `\
You are a stock trading conversation bot and you can help users buy stocks, step by step.
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.
Messages inside [] means that it's a UI element or a user event. For example:
- "[Price of AAPL = 100]" means that an interface of the stock price of AAPL is shown to the user.
- "[User has changed the amount of AAPL to 10]" means that the user has changed the amount of AAPL to 10 in the UI.
If the user requests purchasing a stock, call \`show_stock_purchase_ui\` to show the purchase UI.
If the user just wants the price, call \`show_stock_price\` to show the price.
If you want to show trending stocks, call \`list_stocks\`.
If you want to show events, call \`get_events\`.
If the user wants to sell stock, or complete another impossible task, respond that you are a demo and cannot do that.
Besides that, you can also chat with users and do some calculations if needed.`,
messages: [
...aiState.get().messages.map((message: any) => ({
role: message.role,
content: message.content,
name: message.name
}))
],
text: ({ content, done, delta }) => {
// console.log('AI generated content', content)
if (!textStream) {
textStream = createStreamableValue('')
textNode = <BotMessage content={textStream.value} />
}
if (done) {
textStream.done()
aiState.done({
...aiState.get(),
messages: [
...aiState.get().messages,
{
id: nanoid(),
role: 'assistant',
content: [
{
type: 'text',
text: content
}
]
}
]
})
} else {
textStream.update(delta)
}
return textNode
},
tools: {
listStocks: {
description: 'List three imaginary stocks that are trending.',
parameters: z.object({
stocks: z.array(
z.object({
symbol: z.string().describe('The symbol of the stock'),
price: z.number().describe('The price of the stock'),
delta: z.number().describe('The change in price of the stock')
})
)
}),
generate: async function* ({ stocks }) {
yield (
<>Loading...</>
)
await sleep(1000)
const toolCallId = nanoid()
aiState.done({
...aiState.get(),
messages: [
...aiState.get().messages,
{
id: nanoid(),
role: 'assistant',
content: [
{
type: 'tool-call',
toolName: 'listStocks',
toolCallId,
args: { stocks }
}
]
},
{
id: nanoid(),
role: 'tool',
content: [
{
type: 'tool-result',
toolName: 'listStocks',
toolCallId,
result: stocks
}
]
}
]
})
const finalToolResult = getMoreDetails(stocks)
// Send the tool result to the assistant and get a text response...
return (
// Some text generated as response
)
}
},
I didn't find a direct way to do this by looking at the documentation, even tho I think it should be a common use case |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 2 replies
-
I was able to solve this using a async function submitUserMessage(text: string, images: ImageFile[] = [], files: File[] = []) {
'use server'
const session = await auth()
// IMPR: you should check plans
if (!session?.user?.id && (images.length > 0 || files.length > 0)) {
throw new Error('You need to be authenticated to upload files')
}
const content = await parseContent(text, images, files)
const aiState = getMutableAIState<typeof AI>()
aiState.update({
...aiState.get(),
messages: [
...aiState.get().messages,
{
id: nanoid(),
role: 'user',
content
}
]
})
let textStream: undefined | ReturnType<typeof createStreamableValue<string>>
let textNode: undefined | React.ReactNode
const result = await streamUI({
model: openai('gpt-3.5-turbo'),
initial: <SpinnerMessage />,
system: `\
You are a stock trading conversation bot and you can help users buy stocks, step by step.
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.
Messages inside [] means that it's a UI element or a user event. For example:
- "[Price of AAPL = 100]" means that an interface of the stock price of AAPL is shown to the user.
- "[User has changed the amount of AAPL to 10]" means that the user has changed the amount of AAPL to 10 in the UI.
If the user requests purchasing a stock, call \`show_stock_purchase_ui\` to show the purchase UI.
If the user just wants the price, call \`show_stock_price\` to show the price.
If you want to show trending stocks, call \`list_stocks\`.
If you want to show events, call \`get_events\`.
If the user wants to sell stock, or complete another impossible task, respond that you are a demo and cannot do that.
Besides that, you can also chat with users and do some calculations if needed.`,
messages: [
...aiState.get().messages.map((message: any) => ({
role: message.role,
content: message.content,
name: message.name
}))
],
text: ({ content, done, delta }) => {
// console.log('AI generated content', content)
if (!textStream) {
textStream = createStreamableValue('')
textNode = <BotMessage content={textStream.value} />
}
if (done) {
textStream.done()
aiState.done({
...aiState.get(),
messages: [
...aiState.get().messages,
{
id: nanoid(),
role: 'assistant',
content: [
{
type: 'text',
text: content
}
]
}
]
})
} else {
textStream.update(delta)
}
return textNode
},
tools: {
listStocks: {
description: 'List three imaginary stocks that are trending.',
parameters: z.object({
stocks: z.array(
z.object({
symbol: z.string().describe('The symbol of the stock'),
price: z.number().describe('The price of the stock'),
delta: z.number().describe('The change in price of the stock')
})
)
}),
generate: async function* ({ stocks }) {
yield (
<>Calling listStocks...</>
)
await sleep(1000)
const toolCallId = nanoid()
// I calculate the actual tool result
const finalToolResult = getMoreDetails(stocks)
// I give it to the AI
aiState.done({
...aiState.get(),
messages: [
...aiState.get().messages,
{
id: nanoid(),
role: 'assistant',
content: [
{
type: 'tool-call',
toolName: 'listStocks',
toolCallId,
args: { stocks }
}
]
},
{
id: nanoid(),
role: 'tool',
content: [
{
type: 'tool-result',
toolName: 'listStocks',
toolCallId,
result: stocks
}
]
}
]
})
// Let's get the text response
const newResult = await streamUI({
model: openai('gpt-3.5-turbo'),
initial: <SpinnerMessage />,
system: `\
You are a stock trading conversation bot and you can help users buy stocks, step by step.
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.
Messages inside [] means that it's a UI element or a user event. For example:
- "[Price of AAPL = 100]" means that an interface of the stock price of AAPL is shown to the user.
- "[User has changed the amount of AAPL to 10]" means that the user has changed the amount of AAPL to 10 in the UI.
If the user requests purchasing a stock, call \`show_stock_purchase_ui\` to show the purchase UI.
If the user just wants the price, call \`show_stock_price\` to show the price.
If you want to show trending stocks, call \`list_stocks\`.
If you want to show events, call \`get_events\`.
If the user wants to sell stock, or complete another impossible task, respond that you are a demo and cannot do that.
Besides that, you can also chat with users and do some calculations if needed.`,
messages: [
...aiState.get().messages
],
text: ({ content, done, delta }) => {
if (!textStream) {
textStream = createStreamableValue('')
textNode = <BotMessage content={textStream.value} />
}
if (done) {
textStream.done()
aiState.done({
...aiState.get(),
messages: [
...aiState.get().messages,
{
id: nanoid(),
role: 'assistant',
content: [
{
type: 'text',
text: content
}
]
}
]
})
} else {
textStream.update(delta)
}
return textNode
}
})
// We return a stream of text
return (
newResult.value
)
}
},
// ...
} |
Beta Was this translation helpful? Give feedback.
-
I have the same use case, as we are trying to migrate from the standard OpenAI Assistant + Threads + Runs to the Vercel AI SDK for its auto generated UI components. In our base case we do just what you described:
Is this not intended to be done with streamUI? I see some documentation around recursive function calling but its for Should I be using generateText instead of streamUI and then embed a single streamUI with the results of any recursive calls to tools within generateText? Or are these just cases which can't be combined in the current version? |
Beta Was this translation helpful? Give feedback.
-
Update - we were able to convert parts of our stream UI to generateText to obtain the recursive function/tool calling we needed. Now we want to use the generative output of the streamUI we had before for graphs, etc. Do we then use generateObject instead of streamUI or can we pass the output of generateText into streamUI for the same effect? |
Beta Was this translation helpful? Give feedback.
I was able to solve this using a
streamUI
inside astreamUI
and updating the AI state (hopefully) in the right way. This is the relevant code: