Skip to content

Commit

Permalink
Vue: Add complex response parsing and StreamData support to useChat (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
lgrammel committed Nov 21, 2023
1 parent 4104f74 commit df1ad33
Show file tree
Hide file tree
Showing 8 changed files with 271 additions and 109 deletions.
5 changes: 5 additions & 0 deletions .changeset/afraid-students-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'ai': patch
---

ai/vue: Add complex response parsing and StreamData support to useChat
3 changes: 2 additions & 1 deletion examples/nuxt-openai/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"tailwindcss": "^3.3.3",
"ufo": "^1.2.0",
"unctx": "^2.3.1",
"vue": "^3.3.4"
"vue": "^3.3.4",
"vue-router": "4.2.5"
},
"version": "0.0.0"
}
70 changes: 70 additions & 0 deletions examples/nuxt-openai/pages/function-calling/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<script setup lang="ts">
import { useChat } from 'ai/vue';
import { nanoid } from 'ai';
import type { FunctionCallHandler, Message } from 'ai';
const functionCallHandler: FunctionCallHandler = async (
chatMessages,
functionCall,
) => {
if (functionCall.name === 'eval_code_in_browser') {
if (functionCall.arguments) {
// Parsing here does not always work since it seems that some characters in generated code aren't escaped properly.
const parsedFunctionCallArguments: { code: string } = JSON.parse(
functionCall.arguments,
);
// WARNING: Do NOT do this in real-world applications!
eval(parsedFunctionCallArguments.code);
const functionResponse = {
messages: [
...chatMessages,
{
id: nanoid(),
name: 'eval_code_in_browser',
role: 'function' as const,
content: parsedFunctionCallArguments.code,
},
],
};
return functionResponse;
}
}
};
const { messages, input, handleSubmit } = useChat({
api: '/api/chat-with-functions',
experimental_onFunctionCall: functionCallHandler,
});
// Generate a map of message role to text color
const roleToColorMap: Record<Message['role'], string> = {
system: 'red',
user: 'black',
function: 'blue',
assistant: 'green',
};
</script>

<template>
<div class="flex flex-col w-full max-w-md py-24 mx-auto stretch">
<div
v-for="m in messages"
key="m.id"
class="whitespace-pre-wrap"
:style="{ color: roleToColorMap[m.role] }"
>
<strong>{{ m.role }}:</strong>
{{ m.content || JSON.stringify(m.function_call) }}
<br />
<br />
</div>

<form @submit="handleSubmit">
<input
class="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl"
v-model="input"
placeholder="Say something..."
/>
</form>
</div>
</template>
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
<script setup lang="ts">
import { useChat } from 'ai/vue'
import { useChat } from 'ai/vue';
const { messages, input, handleSubmit } = useChat({
headers: { 'Content-Type': 'application/json' }
})
const { messages, input, handleSubmit } = useChat();
</script>

<template>
Expand Down
100 changes: 100 additions & 0 deletions examples/nuxt-openai/server/api/chat-with-functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import {
OpenAIStream,
StreamingTextResponse,
experimental_StreamData,
} from 'ai';
import OpenAI from 'openai';
import type { ChatCompletionCreateParams } from 'openai/resources/chat';

const functions: ChatCompletionCreateParams.Function[] = [
{
name: 'get_current_weather',
description: 'Get the current weather.',
parameters: {
type: 'object',
properties: {
format: {
type: 'string',
enum: ['celsius', 'fahrenheit'],
description: 'The temperature unit to use.',
},
},
required: ['format'],
},
},
{
name: 'eval_code_in_browser',
description: 'Execute javascript code in the browser with eval().',
parameters: {
type: 'object',
properties: {
code: {
type: 'string',
description: `Javascript code that will be directly executed via eval(). Do not use backticks in your response.
DO NOT include any newlines in your response, and be sure to provide only valid JSON when providing the arguments object.
The output of the eval() will be returned directly by the function.`,
},
},
required: ['code'],
},
},
];

export default defineLazyEventHandler(async () => {
const apiKey = useRuntimeConfig().openaiApiKey;
if (!apiKey) throw new Error('Missing OpenAI API key');
const openai = new OpenAI({
apiKey: apiKey,
});

return defineEventHandler(async (event: any) => {
const { messages } = await readBody(event);

const response = await openai.chat.completions.create({
model: 'gpt-3.5-turbo-0613',
stream: true,
messages,
functions,
});

const data = new experimental_StreamData();
const stream = OpenAIStream(response, {
experimental_onFunctionCall: async (
{ name, arguments: args },
createFunctionCallMessages,
) => {
if (name === 'get_current_weather') {
// Call a weather API here
const weatherData = {
temperature: 20,
unit: args.format === 'celsius' ? 'C' : 'F',
};

data.append({
text: 'Some custom data',
});

const newMessages = createFunctionCallMessages(weatherData);
return openai.chat.completions.create({
messages: [...messages, ...newMessages],
stream: true,
model: 'gpt-3.5-turbo-0613',
});
}
},
onCompletion(completion) {
console.log('completion', completion);
},
onFinal(completion) {
data.close();
},
experimental_streamData: true,
});

data.append({
text: 'Hello, how are you?',
});

return new StreamingTextResponse(stream, {}, data);
});
});
5 changes: 4 additions & 1 deletion packages/core/shared/call-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ export async function callApi({
messages,
...body,
}),
headers,
headers: {
'Content-Type': 'application/json',
...headers,
},
signal: abortController?.()?.signal,
credentials,
}).catch(err => {
Expand Down

0 comments on commit df1ad33

Please sign in to comment.