Skip to content

Commit

Permalink
Svelte: Add complex response parsing and StreamData support to useChat (
Browse files Browse the repository at this point in the history
#757)

Co-authored-by: Max Leiter <max.leiter@vercel.com>
  • Loading branch information
lgrammel and MaxLeiter committed Nov 20, 2023
1 parent c2369df commit 223fde3
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 227 deletions.
5 changes: 5 additions & 0 deletions .changeset/two-carrots-remain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'ai': patch
---

ai/svelte: Add complex response parsing and StreamData support to useChat
1 change: 1 addition & 0 deletions examples/next-openai/app/api/chat-with-functions/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
} from 'ai';
import OpenAI from 'openai';
import type { ChatCompletionCreateParams } from 'openai/resources/chat';

// Create an OpenAI API client (that's edge friendly!)
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY || '',
Expand Down
7 changes: 3 additions & 4 deletions examples/next-openai/app/function-calling/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
'use client';

import { Message } from 'ai/react';
import { useChat } from 'ai/react';
import { ChatRequest, FunctionCallHandler, nanoid } from 'ai';
import { FunctionCallHandler, nanoid } from 'ai';
import { Message, useChat } from 'ai/react';

export default function Chat() {
const functionCallHandler: FunctionCallHandler = async (
Expand Down Expand Up @@ -33,7 +32,7 @@ export default function Chat() {
}
};

const { messages, input, handleInputChange, handleSubmit, data } = useChat({
const { messages, input, handleInputChange, handleSubmit } = useChat({
api: '/api/chat-with-functions',
experimental_onFunctionCall: functionCallHandler,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import OpenAI from 'openai';
import { OpenAIStream, StreamingTextResponse } from 'ai';
import type { CompletionCreateParams } from 'openai/resources/chat';

import {
OpenAIStream,
StreamingTextResponse,
experimental_StreamData,
} from 'ai';
import { env } from '$env/dynamic/private';
import type { ChatCompletionCreateParams } from 'openai/resources/chat';

const openai = new OpenAI({
apiKey: env.OPENAI_API_KEY || '',
});

const functions: CompletionCreateParams.Function[] = [
const functions: ChatCompletionCreateParams.Function[] = [
{
name: 'get_current_weather',
description: 'Get the current weather',
Expand All @@ -29,15 +32,6 @@ const functions: CompletionCreateParams.Function[] = [
required: ['location', 'format'],
},
},
{
name: 'get_current_time',
description: 'Get the current time',
parameters: {
type: 'object',
properties: {},
required: [],
},
},
{
name: 'eval_code_in_browser',
description: 'Execute javascript code in the browser with eval().',
Expand All @@ -57,16 +51,52 @@ const functions: CompletionCreateParams.Function[] = [
];

export async function POST({ request }) {
const { messages, function_call } = await request.json();
const { messages } = await request.json();

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

const stream = OpenAIStream(response);
return new StreamingTextResponse(stream);
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);
}
Original file line number Diff line number Diff line change
@@ -1,81 +1,35 @@
<script lang="ts">
import { useChat } from 'ai/svelte'
import type { ChatRequest, FunctionCallHandler } from 'ai'
import type { FunctionCallHandler } from 'ai'
import { nanoid } from 'ai'
const functionCallHandler: FunctionCallHandler = async (
chatMessages,
functionCall
functionCall,
) => {
if (functionCall.name === 'get_current_weather') {
if (functionCall.arguments) {
const parsedFunctionCallArguments = JSON.parse(functionCall.arguments)
// You now have access to the parsed arguments here (assuming the JSON was valid)
// If JSON is invalid, return an appropriate message to the model so that it may retry?
console.log(parsedFunctionCallArguments)
}
// Generate a fake temperature
const temperature = Math.floor(Math.random() * (100 - 30 + 1) + 30)
// Generate random weather condition
const weather = ['sunny', 'cloudy', 'rainy', 'snowy'][
Math.floor(Math.random() * 4)
]
const functionResponse: ChatRequest = {
messages: [
...chatMessages,
{
id: nanoid(),
name: 'get_current_weather',
role: 'function' as const,
content: JSON.stringify({
temperature,
weather,
info: 'This data is randomly generated and came from a fake weather API!'
})
}
]
}
return functionResponse
} else if (functionCall.name === 'get_current_time') {
const time = new Date().toLocaleTimeString()
const functionResponse: ChatRequest = {
messages: [
...chatMessages,
{
id: nanoid(),
name: 'get_current_time',
role: 'function' as const,
content: JSON.stringify({ time })
}
]
// You can also (optionally) return a list of functions here that the model can call next
// functions
}
return functionResponse
} else if (functionCall.name === 'eval_code_in_browser') {
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
)
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: JSON.stringify(eval(parsedFunctionCallArguments.code))
}
]
}
return functionResponse
content: parsedFunctionCallArguments.code,
},
],
};
return functionResponse;
}
}
}
};
const { messages, input, handleSubmit } = useChat({
api: '/api/chat-with-functions',
Expand Down

0 comments on commit 223fde3

Please sign in to comment.